{"id":65945,"date":"2023-10-02T09:24:16","date_gmt":"2023-10-02T08:24:16","guid":{"rendered":"https:\/\/kinqsta.com\/de\/?p=65945&#038;preview=true&#038;preview_id=65945"},"modified":"2023-10-24T10:16:10","modified_gmt":"2023-10-24T09:16:10","slug":"stripe-java-api","status":"publish","type":"post","link":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/","title":{"rendered":"Ein Leitfaden zur Stripe-Integration in Spring Boot"},"content":{"rendered":"<p>Mit der Zunahme digitaler Transaktionen ist die F\u00e4higkeit, Zahlungsgateways nahtlos zu integrieren, zu einer entscheidenden F\u00e4higkeit f\u00fcr Entwickler geworden. Ob f\u00fcr Marktpl\u00e4tze oder <a href=\"https:\/\/kinqsta.com\/de\/blog\/saas-produkte\/\">SaaS-Produkte<\/a>, ein Zahlungsabwickler ist unerl\u00e4sslich, um die Zahlungen der Nutzer\/innen zu sammeln und zu verarbeiten.<\/p>\n<p>In diesem Artikel erf\u00e4hrst du, wie du <a href=\"https:\/\/kinqsta.com\/de\/blog\/stripe-vs-adyen\/\">Stripe<\/a> in eine <a href=\"https:\/\/spring.io\/projects\/spring-boot\" target=\"_blank\" rel=\"noopener noreferrer\">Spring Boot-Umgebung<\/a> integrierst, wie du Abonnements einrichtest, kostenlose Testversionen anbietest und Selbstbedienungsseiten erstellst, auf denen deine Kunden ihre Rechnungen herunterladen k\u00f6nnen.<br \/>\n<div><\/div><kinsta-auto-toc heading=\"Table of Contents\" exclude=\"last\" list-style=\"arrow\" selector=\"h2\" count-number=\"-1\"><\/kinsta-auto-toc><\/p>\n<h2>Was ist Stripe?<\/h2>\n<p><a href=\"https:\/\/example.com\/en-in\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe<\/a> ist eine weltweit bekannte Plattform zur Zahlungsabwicklung, die in <a href=\"https:\/\/example.com\/en-in\/global\" target=\"_blank\" rel=\"noopener noreferrer\">46 L\u00e4ndern<\/a> verf\u00fcgbar ist. Sie ist eine gute Wahl, wenn du eine Zahlungsintegration in deine Webanwendung einbauen willst, denn sie hat eine gro\u00dfe Reichweite, einen guten Ruf und eine ausf\u00fchrliche Dokumentation.<\/p>\n<h3>Allgemeine Stripe-Konzepte verstehen<\/h3>\n<p>Es ist hilfreich, einige allgemeine Konzepte zu verstehen, die Stripe verwendet, um Zahlungsvorg\u00e4nge zwischen mehreren Parteien zu koordinieren und auszuf\u00fchren. Stripe bietet zwei Ans\u00e4tze f\u00fcr die Implementierung der Zahlungsintegration in deiner Anwendung.<\/p>\n<p>Du kannst entweder die Formulare von Stripe in deine Anwendung einbetten, um den Kunden ein In-App-Erlebnis zu bieten (<a href=\"https:\/\/example.com\/docs\/payments\/payment-intents\" target=\"_blank\" rel=\"noopener noreferrer\">Payment Intent<\/a>), oder die Kunden auf eine von Stripe gehostete Zahlungsseite umleiten, auf der Stripe den Prozess verwaltet und deine Anwendung dar\u00fcber informiert, ob eine Zahlung erfolgreich war oder nicht (<a href=\"https:\/\/example.com\/docs\/payment-links\" target=\"_blank\" rel=\"noopener noreferrer\">Payment Link<\/a>).<\/p>\n<h4>Zahlungsintention<\/h4>\n<p>Bei der Zahlungsabwicklung ist es wichtig, die Kunden- und Produktdaten im Voraus zu erfassen, bevor du sie zur Eingabe ihrer Kartendaten und zur Zahlung aufforderst. Diese Details umfassen die Beschreibung, den Gesamtbetrag, die Zahlungsart und mehr.<\/p>\n<p>Stripe verlangt von dir, dass du diese Daten in deiner Anwendung sammelst und ein <code>PaymentIntent<\/code> Objekt im Backend generierst. Auf diese Weise kann Stripe eine Zahlungsanforderung f\u00fcr diese Absicht formulieren. Nach Abschluss der Zahlung kannst du die Zahlungsdetails, einschlie\u00dflich des Verwendungszwecks, immer \u00fcber das <code>PaymentIntent<\/code> Objekt abrufen.<\/p>\n<h4>Zahlungslinks<\/h4>\n<p>Um die Komplexit\u00e4t der Integration von Stripe direkt in deine Codebasis zu vermeiden, kannst du <a href=\"https:\/\/example.com\/docs\/payments\/checkout\/how-checkout-works\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe Checkouts<\/a> als gehostete Zahlungsl\u00f6sung nutzen. Wie bei der Erstellung von <code>PaymentIntent<\/code> erstellst du ein <code>CheckoutSession<\/code> mit den Zahlungs- und Kundendaten. Anstatt eine In-App <code>PaymentIntent<\/code> zu initiieren, generiert die <code>CheckoutSession<\/code> einen Zahlungslink, \u00fcber den du deine Kunden weiterleitest. So sieht eine gehostete Zahlungsseite aus:<\/p>\n<figure id=\"attachment_163048\" aria-describedby=\"caption-attachment-163048\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163048 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/stripe-hosted-checkout-page-1024x522.png\" alt=\"Die von Stripe gehostete Kassenseite mit den Rechnungsdetails auf der linken Seite und dem Formular zur Erfassung der Zahlungsdetails auf der rechten Seite.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163048\" class=\"wp-caption-text\">Die von Stripe gehostete Kassenseite<\/figcaption><\/figure>\n<p>Nach der Zahlung leitet Stripe zur\u00fcck zu deiner Anwendung und erm\u00f6glicht so Aufgaben nach der Zahlung wie Best\u00e4tigungen und Lieferanfragen. Konfiguriere einen Backend-Webhook, um Stripe zu aktualisieren, damit die Zahlungsdaten auch dann erhalten bleiben, wenn die Kunden die Seite nach der Zahlung versehentlich schlie\u00dfen.<\/p>\n<p>Diese Methode ist zwar effektiv, aber es fehlt ihr an Flexibilit\u00e4t bei der Anpassung und Gestaltung. Au\u00dferdem kann es schwierig sein, sie f\u00fcr mobile Apps richtig einzurichten, wo eine native Integration viel nahtloser aussehen w\u00fcrde.<\/p>\n<h4>API-Schl\u00fcssel<\/h4>\n<p>Wenn du mit der Stripe-API arbeitest, brauchst du API-Schl\u00fcssel f\u00fcr deine Client- und Server-Anwendungen, um mit dem Stripe-Backend zu interagieren. Du kannst auf deine Stripe-API-Schl\u00fcssel in <a href=\"https:\/\/dashboard.example.com\/test\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">deinem Stripe-Entwickler-Dashboard<\/a> zugreifen. So w\u00fcrde es aussehen:<\/p>\n<figure id=\"attachment_163050\" aria-describedby=\"caption-attachment-163050\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163050 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/stripe-dashboard-api-keys-1024x522.png\" alt=\"Der Entwicklerbereich des Stripe-Dashboards zeigt die Registerkarte API-Schl\u00fcssel.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163050\" class=\"wp-caption-text\">Das Stripe-Dashboard mit den API-Schl\u00fcsseln<\/figcaption><\/figure>\n<h3>Wie funktionieren Zahlungen in Stripe?<\/h3>\n<p>Um zu verstehen, wie Zahlungen in Stripe funktionieren, musst du alle beteiligten Akteure verstehen. An jeder Zahlungstransaktion sind vier Akteure beteiligt:<\/p>\n<ol>\n<li><strong>Kunde<\/strong>: Die Person, die f\u00fcr eine Dienstleistung\/ein Produkt bezahlen will.<\/li>\n<li><strong>H\u00e4ndler<\/strong>: Du, der Gesch\u00e4ftsinhaber, bist f\u00fcr den Empfang von Zahlungen und den Verkauf von Dienstleistungen\/Produkten verantwortlich.<\/li>\n<li><strong>Acquirer<\/strong>: Eine Bank, die Zahlungen f\u00fcr dich (den H\u00e4ndler) abwickelt und deine Zahlungsanfragen an die Banken deiner Kunden weiterleitet. Acquirer k\u00f6nnen mit Dritten zusammenarbeiten, um die Zahlungen zu bearbeiten.<\/li>\n<li><strong>Ausstellende Bank<\/strong>: Die Bank, die Kredite vergibt und Karten und andere Zahlungsmittel an Verbraucher ausgibt.<\/li>\n<\/ol>\n<p>Hier ist ein typischer Zahlungsfluss zwischen diesen Akteuren auf einer sehr hohen Ebene.<\/p>\n<figure id=\"attachment_163049\" aria-describedby=\"caption-attachment-163049\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163049 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/how-online-payments-work-1024x201.png\" alt=\"Ein grundlegender Arbeitsablauf, der zeigt, wie Online-Zahlungen vom Kunden, H\u00e4ndler, Acquirer und der ausstellenden Bank abgewickelt werden.\" width=\"1024\" height=\"201\"><figcaption id=\"caption-attachment-163049\" class=\"wp-caption-text\">So funktionieren Online-Zahlungen<\/figcaption><\/figure>\n<p>Der Kunde teilt dem H\u00e4ndler mit, dass er bereit ist zu zahlen. Der H\u00e4ndler leitet dann die Zahlungsdaten an seine Acquiring-Bank weiter, die die Zahlung von der Bank des Kunden einzieht und den H\u00e4ndler \u00fcber den Erfolg der Zahlung informiert.<\/p>\n<p>Dies ist ein sehr detaillierter \u00dcberblick \u00fcber den Zahlungsprozess. Als H\u00e4ndler musst du dich nur um die Erfassung der Zahlungsabsicht, die Weiterleitung an den Zahlungsabwickler und die Bearbeitung des Zahlungsergebnisses k\u00fcmmern. Wie bereits erw\u00e4hnt, gibt es jedoch zwei M\u00f6glichkeiten, wie du das machen kannst.<\/p>\n<p>Wenn du eine von Stripe verwaltete Kaufabwicklung erstellst, bei der Stripe sich um die Erfassung der Zahlungsdaten k\u00fcmmert, sieht der typische Ablauf folgenderma\u00dfen aus:<\/p>\n<figure id=\"attachment_163051\" aria-describedby=\"caption-attachment-163051\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163051 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/stripe-hosted-payment-workflow-1024x749.png\" alt=\"Der Stripe Hosted Checkout-Zahlungsworkflow zeigt, wie die Zahlung zwischen dem Client, dem Server, der Stripe API und der Hosted Stripe Checkout-Seite abgewickelt wird.\" width=\"1024\" height=\"749\"><figcaption id=\"caption-attachment-163051\" class=\"wp-caption-text\">Der von Stripe verwaltete Checkout-Zahlungsablauf. (<strong>Quelle:<\/strong> <a href=\"https:\/\/example.com\/docs\/payments\/checkout\/how-checkout-works#:~:text=The%20Checkout%20Session%20provides%20a,checkout.session.completed%20event.\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe Docs<\/a>)<\/figcaption><\/figure>\n<p>Bei benutzerdefinierten Zahlungsabl\u00e4ufen liegt es wirklich an dir. Du kannst die Interaktion zwischen deinem Client, Server, Kunden und der Stripe-API nach den Bed\u00fcrfnissen deiner Anwendung gestalten. Du kannst diesen Workflow je nach Bedarf um Adresserfassung, Rechnungserstellung, Stornierung, kostenlose Tests usw. erweitern.<\/p>\n<p>Jetzt, wo du wei\u00dft, wie Stripe-Zahlungen funktionieren, kannst du damit beginnen, sie in deine Java-Anwendung zu integrieren.<\/p>\n<h2>Stripe-Integration in Spring Boot-Anwendung<\/h2>\n<p>Um mit der Stripe-Integration zu beginnen, musst du eine Frontend-Anwendung erstellen, die mit dem Java-Backend interagiert und Zahlungen ausl\u00f6st. In diesem Leitfaden baust du eine React-Anwendung, um verschiedene Zahlungsarten und Abonnements auszul\u00f6sen, damit du die Mechanismen besser verstehst.<\/p>\n<p><strong>Hinweis<\/strong>: In diesem Tutorial geht es nicht darum, eine komplette E-Commerce-Website zu erstellen, sondern vor allem darum, dich durch den einfachen Prozess der Integration von Stripe in Spring Boot zu f\u00fchren.<\/p>\n<h3>Einrichten der Frontend- und Backend-Projekte<\/h3>\n<p>Erstelle ein neues Verzeichnis und r\u00fcste ein React-Projekt mit Vite, indem du den folgenden Befehl ausf\u00fchrst:<\/p>\n<pre><code class=\"language-bash\">npm create vite@latest<\/code><\/pre>\n<p>Setze den Projektnamen auf <strong>Frontend<\/strong> (oder einen beliebigen Namen), das Framework auf <strong>React<\/strong> und die Variante auf <strong>TypeScript<\/strong>. Navigiere in das Projektverzeichnis und installiere Chakra UI f\u00fcr die schnelle Erstellung von UI-Elementen, indem du den folgenden Befehl ausf\u00fchrst:<\/p>\n<pre><code class=\"language-bash\">npm i @chakra-ui\/react @emotion\/react @emotion\/styled framer-motion @chakra-ui\/icons<\/code><\/pre>\n<p>Au\u00dferdem installierst du <code>react-router-dom<\/code> in deinem Projekt f\u00fcr das clientseitige Routing, indem du den folgenden Befehl ausf\u00fchrst:<\/p>\n<pre><code class=\"language-bash\">npm i react-router-dom<\/code><\/pre>\n<p>Jetzt kannst du mit der Erstellung deiner Frontend-Anwendung beginnen. Hier ist die Homepage, die du erstellen wirst.<\/p>\n<figure id=\"attachment_163052\" aria-describedby=\"caption-attachment-163052\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163052 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/completed-home-page-1024x522.png\" alt=\"Die fertige Startseite f\u00fcr die Frontend-Anwendung mit einer Kopfzeile und Schaltfl\u00e4chen f\u00fcr den Zugriff auf alle Seiten der Anwendung.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163052\" class=\"wp-caption-text\">Die fertige Startseite f\u00fcr die Frontend-Anwendung.<\/figcaption><\/figure>\n<p>Wenn du auf eine beliebige Schaltfl\u00e4che auf dieser Seite klickst, gelangst du zu separaten Kassenseiten mit Zahlungsformularen. Um zu beginnen, erstelle einen neuen Ordner namens <strong>routes<\/strong> in deinem <strong>frontend\/src<\/strong> Verzeichnis. In diesem Ordner erstellst du eine Datei <strong>Home.tsx<\/strong>. Diese Datei enth\u00e4lt den Code f\u00fcr die Home-Route deiner Anwendung (<code>\/<\/code>). F\u00fcge den folgenden Code in die Datei ein:<\/p>\n<pre><code class=\"language-typescript\">import {Button, Center, Heading, VStack} from \"@chakra-ui\/react\";\n\nimport { useNavigate } from \"react-router-dom\";\n\nfunction Home() {\n    const navigate = useNavigate()\n    const navigateToIntegratedCheckout = () =&gt; {\n        navigate(\"\/integrated-checkout\")\n    }\n\n    const navigateToHostedCheckout = () =&gt; {\n        navigate(\"\/hosted-checkout\")\n    }\n\n    const navigateToNewSubscription = () =&gt; {\n        navigate(\"\/new-subscription\")\n    }\n\n    const navigateToCancelSubscription = () =&gt; {\n        navigate(\"\/cancel-subscription\")\n    }\n\n    const navigateToSubscriptionWithTrial = () =&gt; {\n        navigate(\"\/subscription-with-trial\")\n    }\n\n    const navigateToViewInvoices = () =&gt; {\n        navigate(\"\/view-invoices\")\n    }\n\n    return (\n        &lt;&gt;\n            &lt;Center h={'100vh'} color='black'&gt;\n                &lt;VStack spacing='24px'&gt;\n                    &lt;Heading&gt;Stripe Payments With React & Java&lt;\/Heading&gt;\n                    &lt;Button\n                        colorScheme={'teal'}\n                        onClick={navigateToIntegratedCheckout}&gt;\n                        Integrated Checkout\n                    &lt;\/Button&gt;\n                    &lt;Button\n                        colorScheme={'blue'}\n                        onClick={navigateToHostedCheckout}&gt;\n                        Hosted Checkout\n                    &lt;\/Button&gt;\n                    &lt;Button\n                        colorScheme={'yellow'}\n                        onClick={navigateToNewSubscription}&gt;\n                        New Subscription\n                    &lt;\/Button&gt;\n                    &lt;Button\n                        colorScheme={'purple'}\n                        onClick={navigateToCancelSubscription}&gt;\n                        Cancel Subscription\n                    &lt;\/Button&gt;\n                    &lt;Button\n                        colorScheme={'facebook'}\n                        onClick={navigateToSubscriptionWithTrial}&gt;\n                        Subscription With Trial\n                    &lt;\/Button&gt;\n                    &lt;Button\n                        colorScheme={'pink'}\n                        onClick={navigateToViewInvoices}&gt;\n                        View Invoices\n                    &lt;\/Button&gt;\n                &lt;\/VStack&gt;\n            &lt;\/Center&gt;\n        &lt;\/&gt;\n    )\n}\n\nexport default Home<\/code><\/pre>\n<p>Um die Navigation in deiner Anwendung zu aktivieren, aktualisiere deine <strong>App.tsx-Datei<\/strong>, um die Klasse <code>RouteProvider<\/code> von <code>react-router-dom<\/code> zu konfigurieren.<\/p>\n<pre><code class=\"language-js\">import Home from \".\/routes\/Home.tsx\";\nimport {\n    createBrowserRouter,\n    RouterProvider,\n} from \"react-router-dom\";\n\nfunction App() {\n\n    const router = createBrowserRouter([\n        {\n            path: \"\/\",\n            element: (\n                &lt;Home\/&gt;\n            ),\n        },\n    ]);\n\n  return (\n    &lt;RouterProvider router={router}\/&gt;\n  )\n}\n\nexport default App<\/code><\/pre>\n<p>F\u00fchre den Befehl <code>npm run dev<\/code> aus, um eine Vorschau deiner Anwendung auf <a href=\"https:\/\/localhost:5173\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/localhost:5173 zu erhalten.<\/a><\/p>\n<p>Damit sind die ersten Schritte f\u00fcr die Frontend-Anwendung abgeschlossen. Als N\u00e4chstes erstellst du eine Backend-Anwendung mit Spring Boot. Um die Anwendung zu initialisieren, kannst du die Website <a href=\"https:\/\/start.spring.io\/\" target=\"_blank\" rel=\"noopener noreferrer\">spring initializr<\/a> verwenden (wenn deine IDE die Erstellung von Spring-Apps unterst\u00fctzt, brauchst du die Website nicht zu verwenden).<\/p>\n<p>IntelliJ IDEA unterst\u00fctzt die Erstellung von Spring Boot-Anwendungen. W\u00e4hle zun\u00e4chst die Option <strong>Neues Projekt<\/strong> in IntelliJ IDEA. W\u00e4hle dann <strong>Spring Initializr<\/strong> aus dem linken Fenster. Gib die Details deines Backend-Projekts ein: Name (<strong>Backend<\/strong>), Speicherort (<strong>stripe-payments-java<\/strong> Verzeichnis), Sprache (<strong>Java<\/strong>) und Typ (<strong>Maven<\/strong>). Als Gruppen- und Artefaktnamen verwendest du <strong>com.kinsta.stripe-java<\/strong> bzw. <strong>backend<\/strong>.<\/p>\n<figure id=\"attachment_163053\" aria-describedby=\"caption-attachment-163053\" style=\"width: 814px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163053 size-full\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/idea-new-project-dialog.png\" alt=\"Der IntelliJ IDEA-Dialog f\u00fcr neue Projekte zeigt die ausgef\u00fcllten Details f\u00fcr das neue Projekt an.\" width=\"814\" height=\"727\"><figcaption id=\"caption-attachment-163053\" class=\"wp-caption-text\">Der IDEA-Dialog f\u00fcr neue Projekte<\/figcaption><\/figure>\n<p>Klicke auf die Schaltfl\u00e4che <strong>Weiter<\/strong>. F\u00fcge dann deinem Projekt Abh\u00e4ngigkeiten hinzu, indem du <strong>Spring Web<\/strong> aus dem Dropdown-Men\u00fc <strong>Web<\/strong> im Abh\u00e4ngigkeitsbereich ausw\u00e4hlst und auf die Schaltfl\u00e4che <strong>Erstellen<\/strong> klickst.<\/p>\n<figure id=\"attachment_163054\" aria-describedby=\"caption-attachment-163054\" style=\"width: 814px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163054 size-full\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/idea-new-project-dialog-dependencies.png\" alt=\"Der IntelliJ IDEA-Assistent f\u00fcr neue Projekte zeigt die Abh\u00e4ngigkeiten an, die der Benutzer f\u00fcr seine neue Anwendung ausgew\u00e4hlt hat.\" width=\"814\" height=\"727\"><figcaption id=\"caption-attachment-163054\" class=\"wp-caption-text\">Auswahl der Abh\u00e4ngigkeiten<\/figcaption><\/figure>\n<p>Damit wird das Java-Projekt erstellt und in deiner IDE ge\u00f6ffnet. Jetzt kannst du mit der Erstellung der verschiedenen Checkout-Flows mit Stripe fortfahren.<\/p>\n<h2>Akzeptieren von Online-Zahlungen f\u00fcr Produktk\u00e4ufe<\/h2>\n<p>Die wichtigste und am h\u00e4ufigsten genutzte Funktion von Stripe ist die Annahme von Einmalzahlungen von Kunden. In diesem Abschnitt lernst du zwei M\u00f6glichkeiten kennen, wie du die Zahlungsabwicklung mit Stripe in deine Anwendung integrieren kannst.<\/p>\n<h3>Hosted Checkout<\/h3>\n<p>Zun\u00e4chst erstellst du eine Checkout-Seite, die einen gehosteten Zahlungsworkflow ausl\u00f6st, bei dem du eine Zahlung nur von deiner Frontend-Anwendung aus ausl\u00f6st. Stripe k\u00fcmmert sich dann um die Erfassung der Kartendaten des Kunden und den Einzug der Zahlung und teilt am Ende nur das Ergebnis des Zahlungsvorgangs mit.<\/p>\n<p>So w\u00fcrde die Kassenseite aussehen:<\/p>\n<figure id=\"attachment_163055\" aria-describedby=\"caption-attachment-163055\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163055 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/completed-hosted-checkout-page-1024x506.png\" alt=\"Die fertige gehostete Kassenseite\" width=\"1024\" height=\"506\"><figcaption id=\"caption-attachment-163055\" class=\"wp-caption-text\">Die fertige gehostete Kassenseite<\/figcaption><\/figure>\n<p>Diese Seite hat drei Hauptkomponenten: <code>CartItem<\/code> &#8211; <code>TotalFooter<\/code> &#8211; zeigt den Gesamtbetrag an; <code>CustomerDetails<\/code> &#8211; sammelt die Kundendaten. Du kannst diese Komponenten wiederverwenden, um Checkout-Formulare f\u00fcr andere Szenarien in diesem Artikel zu erstellen, z. B. f\u00fcr integrierte Checkouts und Abonnements.<\/p>\n<h4>Aufbau des Frontends<\/h4>\n<p>Erstelle einen <strong>Komponentenordner<\/strong> in deinem <strong>Frontend\/src-Verzeichnis<\/strong>. Erstelle im <strong>Komponentenordner<\/strong> eine neue Datei <strong>CartItem.tsx<\/strong> und f\u00fcge den folgenden Code ein:<\/p>\n<pre><code class=\"language-js\">import {Button, Card, CardBody, CardFooter, Heading, Image, Stack, Text, VStack} from \"@chakra-ui\/react\";\n\nfunction CartItem(props: CartItemProps) {\n    return &lt;Card direction={{base: 'column', sm: 'row'}}\n                 overflow='hidden'\n                 width={'xl'}\n                 variant='outline'&gt;\n        &lt;Image\n            objectFit='cover'\n            maxW={{base: '100%', sm: '200px'}}\n            src={props.data.image}\n        \/&gt;\n        &lt;Stack mt='6' spacing='3'&gt;\n            &lt;CardBody&gt;\n                &lt;VStack spacing={'3'} alignItems={\"flex-start\"}&gt;\n                    &lt;Heading size='md'&gt;{props.data.name}&lt;\/Heading&gt;\n                    &lt;VStack spacing={'1'} alignItems={\"flex-start\"}&gt;\n                        &lt;Text&gt;\n                            {props.data.description}\n                        &lt;\/Text&gt;\n                        {(props.mode === \"checkout\" ? &lt;Text&gt;\n                            {\"Quantity: \" + props.data.quantity}\n                        &lt;\/Text&gt; : &lt;&gt;&lt;\/&gt;)}\n                    &lt;\/VStack&gt;\n                &lt;\/VStack&gt;\n            &lt;\/CardBody&gt;\n\n            &lt;CardFooter&gt;\n                &lt;VStack alignItems={'flex-start'}&gt;\n                    &lt;Text color='blue.600' fontSize='2xl'&gt;\n                        {\"$\" + props.data.price}\n                    &lt;\/Text&gt;\n                &lt;\/VStack&gt;\n            &lt;\/CardFooter&gt;\n        &lt;\/Stack&gt;\n    &lt;\/Card&gt;\n}\n\nexport interface ItemData {\n    name: string\n    price: number\n    quantity: number\n    image: string\n    description: string\n    id: string\n}\n\ninterface CartItemProps {\n    data: ItemData\n    mode: \"subscription\" | \"checkout\"\n    onCancelled?: () =&gt; void\n}\n\nexport default CartItem<\/code><\/pre>\n<p>Der obige Code definiert zwei Schnittstellen, die als Typen f\u00fcr die Eigenschaften verwendet werden, die an die Komponente \u00fcbergeben werden. Der Typ <code>ItemData<\/code> wird zur Wiederverwendung in anderen Komponenten exportiert.<\/p>\n<p>Der Code gibt das Layout einer Warenkorbkomponente zur\u00fcck. Er verwendet die \u00fcbergebenen Requisiten, um den Artikel auf dem Bildschirm darzustellen.<\/p>\n<p>Als N\u00e4chstes erstellst du eine Datei <strong>TotalFooter.tsx<\/strong> im <strong>Komponentenverzeichnis<\/strong> und f\u00fcgst den folgenden Code ein:<\/p>\n<pre><code class=\"language-js\">import {Divider, HStack, Text} from \"@chakra-ui\/react\";\n\nfunction TotalFooter(props: TotalFooterProps) {\n    return &lt;&gt;\n        &lt;Divider \/&gt;\n        &lt;HStack&gt;\n            &lt;Text&gt;Total&lt;\/Text&gt;\n            &lt;Text color='blue.600' fontSize='2xl'&gt;\n                {\"$\" + props.total}\n            &lt;\/Text&gt;\n        &lt;\/HStack&gt;\n        {props.mode === \"subscription\" &&\n            &lt;Text fontSize={\"xs\"}&gt;(Monthly, starting today)&lt;\/Text&gt;\n        }\n        {props.mode === \"trial\" &&\n            &lt;Text fontSize={\"xs\"}&gt;(Monthly, starting next month)&lt;\/Text&gt;\n        }\n    &lt;\/&gt;\n}\n\ninterface TotalFooterProps {\n    total: number\n    mode: \"checkout\" | \"subscription\" | \"trial\"\n}\n\nexport default TotalFooter\n<\/code><\/pre>\n<p>Die Komponente <code>TotalFooter<\/code> zeigt den Gesamtwert des Warenkorbs an und verwendet den Wert <code>mode<\/code>, um einen bestimmten Text <a href=\"https:\/\/kinqsta.com\/de\/blog\/react-bedingtes-rendering\/\">anzuzeigen<\/a>.<\/p>\n<p>Schlie\u00dflich erstellst du die Komponente <code>CustomerDetails.tsx<\/code> und f\u00fcgst den folgenden Code ein:<\/p>\n<pre><code class=\"language-js\">import {ItemData} from \".\/CartItem.tsx\";\nimport {Button, Input, VStack} from \"@chakra-ui\/react\";\nimport {useState} from \"react\";\n\nfunction CustomerDetails(props: CustomerDetailsProp) {\n    const [name, setName] = useState(\"\")\n    const [email, setEmail] = useState(\"\")\n    const onCustomerNameChange = (ev: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {\n        setName(ev.target.value)\n    }\n\n\n\n    const onCustomerEmailChange = (ev: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {\n        setEmail(ev.target.value)\n    }\n\n    const initiatePayment = () =&gt; {\n        fetch(process.env.VITE_SERVER_BASE_URL + props.endpoint, {\n            method: \"POST\",\n            headers: {'Content-Type': 'application\/json'},\n            body: JSON.stringify({\n                items: props.data.map(elem =&gt; ({name: elem.name, id: elem.id})),\n                customerName: name,\n                customerEmail: email,\n            })\n        })\n            .then(r =&gt; r.text())\n            .then(r =&gt; {\n                window.location.href = r\n            })\n\n    }\n\n    return &lt;&gt;\n        &lt;VStack spacing={3} width={'xl'}&gt;\n            &lt;Input variant='filled' placeholder='Customer Name' onChange={onCustomerNameChange} value={name}\/&gt;\n            &lt;Input variant='filled' placeholder='Customer Email' onChange={onCustomerEmailChange} value={email}\/&gt;\n            &lt;Button onClick={initiatePayment} colorScheme={'green'}&gt;Checkout&lt;\/Button&gt;\n        &lt;\/VStack&gt;\n    &lt;\/&gt;\n}\n\ninterface CustomerDetailsProp {\n    data: ItemData[]\n    endpoint: string\n}\n\nexport default CustomerDetails\n<\/code><\/pre>\n<p>Der obige Code zeigt ein Formular mit zwei Eingabefeldern an, um den Namen und die E-Mail-Adresse des Benutzers zu erfassen. Wenn der <strong>Checkout-Button<\/strong> angeklickt wird, wird die Methode <code>initiatePayment<\/code> aufgerufen, um die Checkout-Anfrage an das Backend zu senden.<\/p>\n<p>Sie fordert den Endpunkt an, den du der Komponente \u00fcbergeben hast, und sendet die Kundendaten und die Artikel des Warenkorbs als Teil der Anfrage und leitet den Nutzer dann an die vom Server erhaltene URL weiter. Diese URL f\u00fchrt den Nutzer zu einer Kassenseite, die auf dem Server von Stripe gehostet wird. Wie du diese URL erstellst, erf\u00e4hrst du gleich.<\/p>\n<p><strong>Hinweis:<\/strong> Diese Komponente verwendet die Umgebungsvariable <code>VITE_SERVER_BASE_URL<\/code> f\u00fcr die URL des Backend-Servers. Setze sie, indem du eine <strong>.env-Datei<\/strong> im Stammverzeichnis deines Projekts erstellst:<\/p>\n<pre><code class=\"language-bash\">VITE_SERVER_BASE_URL=<a href=\"http:\/\/localhost:8080\">http:\/\/localhost:8080<\/a><\/code><\/pre>\n<p>Alle Komponenten sind nun erstellt. Fahren wir nun fort, die Hosted Checkout Route mit den Komponenten zu erstellen. Dazu erstellst du eine neue Datei <strong>HostedCheckout.tsx<\/strong> in deinem <strong>routes-Ordner<\/strong> mit folgendem Code:<\/p>\n<pre><code class=\"language-js\">import {Center, Heading, VStack} from \"@chakra-ui\/react\";\nimport {useState} from \"react\";\nimport CartItem, {ItemData} from \"..\/components\/CartItem.tsx\";\nimport TotalFooter from \"..\/components\/TotalFooter.tsx\";\nimport CustomerDetails from \"..\/components\/CustomerDetails.tsx\";\nimport {Products} from '..\/data.ts'\n\nfunction HostedCheckout() {\n    const [items] = useState&lt;ItemData[]&gt;(Products)\n    return &lt;&gt;\n        &lt;Center h={'100vh'} color='black'&gt;\n            &lt;VStack spacing='24px'&gt;\n                &lt;Heading&gt;Hosted Checkout Example&lt;\/Heading&gt;\n                {items.map(elem =&gt; {\n                    return &lt;CartItem data={elem} mode={'checkout'}\/&gt;\n                })}\n                &lt;TotalFooter total={30} mode={\"checkout\"}\/&gt;\n                &lt;CustomerDetails data={items} endpoint={\"\/checkout\/hosted\"} mode={\"checkout\"}\/&gt;\n            &lt;\/VStack&gt;\n        &lt;\/Center&gt;\n    &lt;\/&gt;\n}\n\nexport default HostedCheckout\n<\/code><\/pre>\n<p>Diese Route verwendet die drei Komponenten, die du gerade erstellt hast, um einen Checkout-Bildschirm zu erstellen. Alle Komponenten sind als <strong>Kasse<\/strong> konfiguriert und der Endpunkt <code>\/checkout\/hosted<\/code> wird der Formularkomponente zur Verf\u00fcgung gestellt, um die Kassenanfrage korrekt zu initiieren.<\/p>\n<p>Die Komponente verwendet ein <code>Products<\/code> Objekt, um das Item-Array zu f\u00fcllen. In der Praxis kommen diese Daten von deiner Warenkorb-API und enthalten die vom Nutzer ausgew\u00e4hlten Artikel. In diesem Leitfaden wird das Array jedoch mit einer statischen Liste aus einem Skript gef\u00fcllt. Definiere das Array, indem du eine <strong>data.ts-Datei<\/strong> im Stammverzeichnis deines Frontend-Projekts erstellst und den folgenden Code darin speicherst:<\/p>\n<pre><code class=\"language-typescript\">import {ItemData} from \".\/components\/CartItem.tsx\";\n\nexport const Products: ItemData[] = [\n    {\n        description: \"Premium Shoes\",\n        image: \"https:\/\/source.unsplash.com\/NUoPWImmjCU\",\n        name: \"Puma Shoes\",\n        price: 20,\n        quantity: 1,\n        id: \"shoe\"\n    },\n    {\n        description: \"Comfortable everyday slippers\",\n        image: \"https:\/\/source.unsplash.com\/K_gIPI791Jo\",\n        name: \"Nike Sliders\",\n        price: 10,\n        quantity: 1,\n        id: \"slippers\"\n    },\n]\n<\/code><\/pre>\n<p>Diese Datei definiert zwei Elemente im Produkt-Array, die im Warenkorb angezeigt werden. Du kannst die Werte der Produkte beliebig ver\u00e4ndern.<\/p>\n<p>Im letzten Schritt der Entwicklung des Frontends erstellst du zwei neue Routen, um Erfolg und Misserfolg zu behandeln. Die von Stripe gehostete Kassenseite leitet die Nutzer je nach Ergebnis der Transaktion \u00fcber diese beiden Routen in deine Anwendung weiter. Stripe stellt deinen Routen auch transaktionsbezogene Daten zur Verf\u00fcgung, z. B. die ID der Kassensitzung, die du verwenden kannst, um <a href=\"https:\/\/example.com\/docs\/api\/checkout\/sessions\/retrieve\" target=\"_blank\" rel=\"noopener noreferrer\">das entsprechende Kassensitzungsobjekt abzurufen<\/a> und auf kassenbezogene Daten wie die Zahlungsmethode, Rechnungsdetails usw. zuzugreifen.<\/p>\n<p>Dazu erstellst du eine Datei <strong>Success.tsx<\/strong> im Verzeichnis <strong>src\/routes<\/strong> und speicherst den folgenden Code darin:<\/p>\n<pre><code class=\"language-js\">import {Button, Center, Heading, Text, VStack} from \"@chakra-ui\/react\";\nimport {useNavigate} from \"react-router-dom\";\n\nfunction Success() {\n    const queryParams = new URLSearchParams(window.location.search)\n    const navigate = useNavigate()\n    const onButtonClick = () =&gt; {\n        navigate(\"\/\")\n    }\n    return &lt;Center h={'100vh'} color='green'&gt;\n        &lt;VStack spacing={3}&gt;\n            &lt;Heading fontSize={'4xl'}&gt;Success!&lt;\/Heading&gt;\n            &lt;Text color={'black'}&gt;{queryParams.toString().split(\"&\").join(\"n\")}&lt;\/Text&gt;\n            &lt;Button onClick={onButtonClick} colorScheme={'green'}&gt;Go Home&lt;\/Button&gt;\n        &lt;\/VStack&gt;\n    &lt;\/Center&gt;\n}\n\nexport default Success\n<\/code><\/pre>\n<p>Beim Rendern zeigt diese Komponente die Meldung &#8222;Success!&#8220; an und gibt alle URL-Abfrageparameter auf dem Bildschirm aus. Au\u00dferdem enth\u00e4lt sie eine Schaltfl\u00e4che, mit der die Benutzer zur Homepage der Anwendung weitergeleitet werden.<\/p>\n<p>Wenn du eine reale Anwendung entwickelst, ist diese Seite der Ort, an dem du unkritische app-seitige Transaktionen abwickelst, die vom Erfolg der jeweiligen Transaktion abh\u00e4ngen. Wenn du z. B. eine Kassenseite f\u00fcr einen Onlineshop erstellst, k\u00f6nntest du diese Seite nutzen, um dem Nutzer eine Best\u00e4tigung zu geben und ihm mitzuteilen, wann seine gekauften Produkte geliefert werden.<\/p>\n<p>Als N\u00e4chstes erstellst du eine Datei <strong>Failure.tsx<\/strong> mit dem folgenden Code darin:<\/p>\n<pre><code class=\"language-js\">import {Button, Center, Heading, Text, VStack} from \"@chakra-ui\/react\";\nimport {useNavigate} from \"react-router-dom\";\n\nfunction Failure() {\n    const queryParams = new URLSearchParams(window.location.search)\n    const navigate = useNavigate()\n    const onButtonClick = () =&gt; {\n        navigate(\"\/\")\n    }\n\n    return &lt;Center h={'100vh'} color='red'&gt;\n        &lt;VStack spacing={3}&gt;\n            &lt;Heading fontSize={'4xl'}&gt;Failure!&lt;\/Heading&gt;\n            &lt;Text color={'black'}&gt;{queryParams.toString().split(\"&\").join(\"n\")}&lt;\/Text&gt;\n            &lt;Button onClick={onButtonClick} colorScheme={'red'}&gt;Try Again&lt;\/Button&gt;\n        &lt;\/VStack&gt;\n    &lt;\/Center&gt;\n}\n\nexport default Failure\n<\/code><\/pre>\n<p>Diese Komponente \u00e4hnelt der <strong>Success.tsx<\/strong> und zeigt die Meldung &#8222;Failure!&#8220; an, wenn sie gerendert wird.<\/p>\n<aside role=\"note\" class=\"wp-block-kinsta-notice is-style-info\">\n            <h3>Info<\/h3>\n        <p>Vermeide es, kritische Informationen zu \u00fcbermitteln oder kritische Vorg\u00e4nge auf der Erfolgs- oder Fehlerseite auszuf\u00fchren, da der Kunde m\u00f6glicherweise nie auf eine der beiden Seiten gelangt, wenn er den Browser-Tab schlie\u00dft oder die Verbindung verliert.<\/p>\n<\/aside>\n\n<p>F\u00fcr wichtige Aufgaben wie die Produktlieferung, das Versenden von E-Mails oder andere kritische Teile deines Kaufprozesses solltest du <a href=\"https:\/\/example.com\/docs\/webhooks\" target=\"_blank\" rel=\"noopener noreferrer\">Webhooks<\/a> verwenden. Webhooks sind API-Routen auf deinem Server, die Stripe aufrufen kann, wenn eine Transaktion stattfindet.<\/p>\n<p>Der Webhook erh\u00e4lt die vollst\u00e4ndigen Transaktionsdetails (\u00fcber das <code>CheckoutSession<\/code> Objekt), sodass du sie in deiner Anwendungs-Datenbank protokollieren und entsprechende Erfolgs- oder Misserfolgs-Workflows ausl\u00f6sen kannst. Da dein Server f\u00fcr Stripe immer erreichbar ist, entgehen dir keine Transaktionen, was die konsistente Funktionalit\u00e4t deines Onlineshops gew\u00e4hrleistet.<\/p>\n<p>Zum Schluss aktualisierst du die Datei <strong>App.tsx<\/strong> so, dass sie wie folgt aussieht:<\/p>\n<pre><code class=\"language-js\">import Home from \".\/routes\/Home.tsx\";\nimport {createBrowserRouter, RouterProvider,} from \"react-router-dom\";\nimport HostedCheckout from \".\/routes\/HostedCheckout.tsx\";\nimport Success from \".\/routes\/Success.tsx\";\nimport Failure from \".\/routes\/Failure.tsx\";\n\nfunction App() {\n\n    const router = createBrowserRouter([\n        {\n            path: \"\/\",\n            element: (\n                &lt;Home\/&gt;\n            ),\n        },\n        {\n            path: \"\/hosted-checkout\",\n            element: (\n                &lt;HostedCheckout\/&gt;\n            )\n        },\n        {\n            path: '\/success',\n            element: (\n                &lt;Success\/&gt;\n            )\n        },\n        {\n            path: '\/failure',\n            element: (\n                &lt;Failure\/&gt;\n            )\n        },\n    ]);\n\n    return (\n        &lt;RouterProvider router={router}\/&gt;\n    )\n}\n\nexport default App\n<\/code><\/pre>\n<p>Dadurch wird sichergestellt, dass die Komponenten <code>Success<\/code> und <code>Failure<\/code> auf den Routen <code>\/success<\/code> bzw. <code>\/failure<\/code> gerendert werden.<\/p>\n<p>Damit ist die Einrichtung des Frontends abgeschlossen. Als N\u00e4chstes richtest du das Backend ein, um den Endpunkt <code>\/checkout\/hosted<\/code> zu erstellen.<\/p>\n<h4>Erstellen des Backends<\/h4>\n<p>\u00d6ffne das Backend-Projekt und installiere das Stripe SDK, indem du die folgenden Zeilen in das Array dependencies in deiner <strong>pom.xml-Datei<\/strong> einf\u00fcgst:<\/p>\n<pre><code class=\"language-bash\">        &lt;dependency&gt;\n            &lt;groupId&gt;com.stripe&lt;\/groupId&gt;\n            &lt;artifactId&gt;stripe-java&lt;\/artifactId&gt;\n            &lt;version&gt;22.29.0&lt;\/version&gt;\n        &lt;\/dependency&gt;\n<\/code><\/pre>\n<p>Als N\u00e4chstes <a href=\"https:\/\/www.jetbrains.com\/help\/idea\/delegate-build-and-run-actions-to-maven.html\">l\u00e4dst du die Maven-\u00c4nderungen<\/a> in dein Projekt, um die Abh\u00e4ngigkeiten zu installieren. Wenn deine IDE dies nicht \u00fcber die Benutzeroberfl\u00e4che unterst\u00fctzt, f\u00fchre entweder den Befehl <code>maven dependency:resolve<\/code> oder <code>maven install<\/code> aus. Wenn du nicht \u00fcber die <code>maven<\/code> CLI verf\u00fcgst, verwende den <code>mvnw<\/code> Wrapper von Spring initializr, wenn du das Projekt erstellst.<\/p>\n<p>Sobald die Abh\u00e4ngigkeiten installiert sind, erstellst du einen neuen REST-Controller, der die eingehenden HTTP-Anfragen f\u00fcr deine Backend-Anwendung bearbeitet. Dazu erstellst du eine Datei <strong>PaymentController.java<\/strong> im Verzeichnis <strong>src\/main\/java\/com\/kinsta\/stripe-java\/backend<\/strong> und f\u00fcgst den folgenden Code hinzu:<\/p>\n<pre><code class=\"language-bash\">package com.kinsta.stripejava.backend;\n\nimport com.stripe.Stripe;\nimport com.stripe.exception.StripeException;\nimport com.stripe.model.Customer;\nimport com.stripe.model.Product;\nimport com.stripe.model.checkout.Session;\nimport com.stripe.param.checkout.SessionCreateParams;\nimport com.stripe.param.checkout.SessionCreateParams.LineItem.PriceData;\nimport org.springframework.web.bind.annotation.CrossOrigin;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@CrossOrigin\npublic class PaymentController {\n\n    String STRIPE_API_KEY = System.getenv().get(\"STRIPE_API_KEY\");\n\n    @PostMapping(\"\/checkout\/hosted\")\n    String hostedCheckout(@RequestBody RequestDTO requestDTO) throws StripeException {\n      return \"Hello World!\";\n    }\n\n}\n<\/code><\/pre>\n<p>Der obige Code importiert wichtige Stripe-Abh\u00e4ngigkeiten und richtet die Klasse <code>PaymentController<\/code> ein. Diese Klasse tr\u00e4gt zwei Annotationen: <code>@RestController<\/code> und <code>@CrossOrigin<\/code>. Die Annotation <code>@RestController<\/code> weist Spring Boot an, diese Klasse als Controller zu behandeln, und ihre Methoden k\u00f6nnen nun <code>@Mapping<\/code> Annotationen verwenden, um eingehende HTTP-Anfragen zu bearbeiten.<\/p>\n<p>Die Annotation <code>@CrossOrigin<\/code> kennzeichnet alle in dieser Klasse definierten Endpunkte als offen f\u00fcr alle Herk\u00fcnfte gem\u00e4\u00df den CORS-Regeln. Aufgrund potenzieller Sicherheitsl\u00fccken in verschiedenen Internet-Dom\u00e4nen wird von dieser Praxis in der Produktion jedoch abgeraten.<\/p>\n<p>Um optimale Ergebnisse zu erzielen, ist es ratsam, sowohl Backend- als auch Frontend-Server auf derselben Domain zu hosten, um CORS-Probleme zu umgehen. Wenn das nicht m\u00f6glich ist, kannst du alternativ die Dom\u00e4ne deines Frontend-Clients (der Anfragen an den Backend-Server sendet) mit der <code>@CrossOrigin<\/code> -Annotation angeben, etwa so:<\/p>\n<pre><code class=\"language-java\">@CrossOrigin(origins = \"http:\/\/frontend.com\")<\/code><\/pre>\n<p>Die Klasse <code>PaymentController<\/code> extrahiert den Stripe-API-Schl\u00fcssel aus den Umgebungsvariablen, um ihn sp\u00e4ter dem Stripe SDK zur Verf\u00fcgung zu stellen. Wenn du die Anwendung ausf\u00fchrst, musst du der Anwendung deinen Stripe-API-Schl\u00fcssel \u00fcber Umgebungsvariablen zur Verf\u00fcgung stellen.<\/p>\n<p>Lokal kannst du eine neue Umgebungsvariable in deinem System erstellen, entweder tempor\u00e4r (indem du eine <code>KEY=VALUE<\/code> Phrase vor dem Befehl zum Starten deines Entwicklungsservers hinzuf\u00fcgst) oder dauerhaft (indem du die Konfigurationsdateien deines Terminals aktualisierst oder eine Umgebungsvariable in der Systemsteuerung von Windows setzt).<\/p>\n<p>In Produktionsumgebungen bietet dir dein Deployment Provider (z. B. Kinsta) eine separate Option, um die von deiner Anwendung verwendeten Umgebungsvariablen einzutragen.<\/p>\n<p>Wenn du IntelliJ IDEA (oder eine \u00e4hnliche IDE) verwendest, klickst du oben rechts in der IDE auf <strong>Run Configurations<\/strong> und dann in der sich \u00f6ffnenden Dropdown-Liste auf die Option <strong>Edit Configurations<\/strong>&#8230;, um deinen Run-Befehl zu aktualisieren und die Umgebungsvariable zu setzen.<\/p>\n<figure id=\"attachment_163056\" aria-describedby=\"caption-attachment-163056\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163056 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/run-debug-configurations-dialog-box-1024x420.png\" alt=\"Das IntelliJ IDEA-Fenster zeigt, von wo aus du auf die Einstellungen f\u00fcr die Run\/Debug-Konfigurationen zugreifen kannst.\" width=\"1024\" height=\"420\"><figcaption id=\"caption-attachment-163056\" class=\"wp-caption-text\">Das Dialogfeld f\u00fcr die Ausf\u00fchrungs-\/Debug-Konfigurationen wird ge\u00f6ffnet<\/figcaption><\/figure>\n<p>Es \u00f6ffnet sich ein Dialogfeld, in dem du die Umgebungsvariablen f\u00fcr deine Anwendung im Feld <strong>Umgebungsvariablen<\/strong> angeben kannst. Gib die Umgebungsvariable <code>STRIPE_API_KEY<\/code> im Format <code>VAR1=VALUE<\/code> ein. Deinen API-Schl\u00fcssel findest du auf der <a href=\"https:\/\/dashboard.example.com\/account\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe Developers Website<\/a>. Du musst den Wert des <strong>geheimen Schl\u00fcssels<\/strong> von dieser Seite angeben.<\/p>\n<figure id=\"attachment_163057\" aria-describedby=\"caption-attachment-163057\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163057 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/stripe-dashboard-showing-api-keys-1024x504.png\" alt=\" Das Stripe-Dashboard mit einem Pfeil, der anzeigt, wo du nach API-Schl\u00fcsseln suchen kannst. Die Schl\u00fcssel sind geschw\u00e4rzt, um sensible Informationen zu verbergen.\" width=\"1024\" height=\"504\"><figcaption id=\"caption-attachment-163057\" class=\"wp-caption-text\">Das Stripe-Dashboard zeigt die API-Schl\u00fcssel an<\/figcaption><\/figure>\n<p>Wenn du es noch nicht getan hast, <a href=\"https:\/\/dashboard.example.com\/register\" target=\"_blank\" rel=\"noopener noreferrer\">erstelle ein neues Stripe-Konto<\/a>, um Zugang zu den API-Schl\u00fcsseln zu erhalten.<\/p>\n<p>Sobald du den API-Schl\u00fcssel eingerichtet hast, kannst du den Endpunkt erstellen. Dieser Endpunkt sammelt die Kundendaten (Name und E-Mail), erstellt ein Kundenprofil in Stripe, falls noch keines vorhanden ist, und erstellt eine <a href=\"https:\/\/example.com\/docs\/payments\/checkout\/how-checkout-works\" target=\"_blank\" rel=\"noopener noreferrer\">Kassensitzung<\/a>, damit die Nutzer\/innen die Artikel im Warenkorb bezahlen k\u00f6nnen.<\/p>\n<p>So sieht der Code f\u00fcr die Methode <code>hostedCheckout<\/code> aus:<\/p>\n<pre><code class=\"language-java\">    @PostMapping(\"\/checkout\/hosted\")\n    String hostedCheckout(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        Stripe.apiKey = STRIPE_API_KEY;\n        String clientBaseURL = System.getenv().get(\"CLIENT_BASE_URL\");\n\n        \/\/ Start by finding an existing customer record from Stripe or creating a new one if needed\n        Customer customer = CustomerUtil.findOrCreateCustomer(requestDTO.getCustomerEmail(), requestDTO.getCustomerName());\n\n        \/\/ Next, create a checkout session by adding the details of the checkout\n        SessionCreateParams.Builder paramsBuilder =\n                SessionCreateParams.builder()\n                        .setMode(SessionCreateParams.Mode.PAYMENT)\n                        .setCustomer(customer.getId())\n                        .setSuccessUrl(clientBaseURL + \"\/success?session_id={CHECKOUT_SESSION_ID}\")\n                        .setCancelUrl(clientBaseURL + \"\/failure\");\n\n        for (Product product : requestDTO.getItems()) {\n            paramsBuilder.addLineItem(\n                    SessionCreateParams.LineItem.builder()\n                            .setQuantity(1L)\n                            .setPriceData(\n                                    PriceData.builder()\n                                            .setProductData(\n                                                    PriceData.ProductData.builder()\n                                                            .putMetadata(\"app_id\", product.getId())\n                                                            .setName(product.getName())\n                                                            .build()\n                                            )\n                                            .setCurrency(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getCurrency())\n                                            .setUnitAmountDecimal(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getUnitAmountDecimal())\n                                            .build())\n                            .build());\n        }\n\n        }\n\n        Session session = Session.create(paramsBuilder.build());\n\n        return session.getUrl();\n    }\n<\/code><\/pre>\n<p>Beim Aufbau der Kassensitzung verwendet der Code den Namen des Produkts, den er vom Kunden erhalten hat, aber nicht die Preisangaben aus der Anfrage. Dieser Ansatz verhindert potenzielle Preismanipulationen auf Kundenseite, bei denen b\u00f6swillige Akteure reduzierte Preise in der Checkout-Anfrage senden k\u00f6nnten, um weniger f\u00fcr Produkte und Dienstleistungen zu bezahlen.<\/p>\n<p>Um dies zu verhindern, fragt die Methode <code>hostedCheckout<\/code> deine Produktdatenbank ab (\u00fcber <code>ProductDAO<\/code>), um den korrekten Artikelpreis abzurufen.<\/p>\n<p>Au\u00dferdem bietet Stripe verschiedene <code>Builder<\/code> Klassen an, die dem Builder Design Pattern folgen. Diese Klassen helfen bei der Erstellung von Parameterobjekten f\u00fcr Stripe-Anfragen. Das mitgelieferte Codeschnipsel verweist auch auf Umgebungsvariablen, um die URL der Client-Anwendung abzurufen. Diese URL wird ben\u00f6tigt, damit das Checkout-Sitzungsobjekt nach erfolgreichen oder fehlgeschlagenen Zahlungen entsprechend weitergeleitet wird.<\/p>\n<p>Um diesen Code auszuf\u00fchren, musst du die URL der Kundenanwendung \u00fcber Umgebungsvariablen festlegen, \u00e4hnlich wie bei der Angabe des Stripe-API-Schl\u00fcssels. Da die Client-Anwendung \u00fcber Vite l\u00e4uft, sollte die lokale App-URL http:\/\/localhost:5173 lauten. F\u00fcge sie \u00fcber deine IDE, dein Terminal oder dein Systemsteuerungspanel in deine Umgebungsvariablen ein.<\/p>\n<pre><code class=\"language-bash\">CLIENT_BASE_URL=http:\/\/localhost:5173<\/code><\/pre>\n<p>Gib der Anwendung au\u00dferdem eine <code>ProductDAO<\/code>, um die Produktpreise abzurufen. Data Access Object (DAO) interagiert mit Datenquellen (z. B. Datenbanken), um auf app-bezogene Daten zuzugreifen. Das Einrichten einer Produktdatenbank w\u00fcrde den Rahmen dieses Leitfadens sprengen, aber eine einfache Implementierung w\u00e4re es, eine neue Datei <strong>ProductDAO.java<\/strong> im selben Verzeichnis wie <strong>PaymentController.java<\/strong> hinzuzuf\u00fcgen und den folgenden Code einzuf\u00fcgen:<\/p>\n<pre><code class=\"language-java\">package com.kinsta.stripejava.backend;\n\nimport com.stripe.model.Price;\nimport com.stripe.model.Product;\n\nimport java.math.BigDecimal;\n\npublic class ProductDAO {\n\n    static Product[] products;\n\n    static {\n        products = new Product[4];\n\n        Product sampleProduct = new Product();\n        Price samplePrice = new Price();\n\n        sampleProduct.setName(\"Puma Shoes\");\n        sampleProduct.setId(\"shoe\");\n        samplePrice.setCurrency(\"usd\");\n        samplePrice.setUnitAmountDecimal(BigDecimal.valueOf(2000));\n        sampleProduct.setDefaultPriceObject(samplePrice);\n        products[0] = sampleProduct;\n\n        sampleProduct = new Product();\n        samplePrice = new Price();\n\n        sampleProduct.setName(\"Nike Sliders\");\n        sampleProduct.setId(\"slippers\");\n        samplePrice.setCurrency(\"usd\");\n        samplePrice.setUnitAmountDecimal(BigDecimal.valueOf(1000));\n        sampleProduct.setDefaultPriceObject(samplePrice);\n        products[1] = sampleProduct;\n\n        sampleProduct = new Product();\n        samplePrice = new Price();\n\n        sampleProduct.setName(\"Apple Music+\");\n        sampleProduct.setId(\"music\");\n        samplePrice.setCurrency(\"usd\");\n        samplePrice.setUnitAmountDecimal(BigDecimal.valueOf(499));\n        sampleProduct.setDefaultPriceObject(samplePrice);\n        products[2] = sampleProduct;\n\n    }\n\n    public static Product getProduct(String id) {\n\n        if (\"shoe\".equals(id)) {\n            return products[0];\n        } else if (\"slippers\".equals(id)) {\n            return products[1];\n        } else if (\"music\".equals(id)) {\n            return products[2];\n        } else return new Product();\n\n    }\n}\n<\/code><\/pre>\n<p>Damit wird ein Array mit Produkten initialisiert und du kannst die Produktdaten anhand ihrer Kennung (ID) abfragen. Au\u00dferdem musst du ein <a href=\"https:\/\/en.wikipedia.org\/wiki\/Data_transfer_object\" target=\"_blank\" rel=\"noopener noreferrer\">DTO<\/a> (Data Transfer Object) erstellen, damit Spring Boot die vom Client eingehenden Nutzdaten automatisch serialisieren und dir ein einfaches Objekt f\u00fcr den Zugriff auf die Daten zur Verf\u00fcgung stellen kann. Dazu erstellst du eine neue Datei <strong>RequestDTO.java<\/strong> und f\u00fcgst den folgenden Code ein:<\/p>\n<pre><code class=\"language-java\">package com.kinsta.stripejava.backend;\n\nimport com.stripe.model.Product;\n\npublic class RequestDTO {\n    Product[] items;\n    String customerName;\n    String customerEmail;\n\n    public Product[] getItems() {\n        return items;\n    }\n\n    public String getCustomerName() {\n        return customerName;\n    }\n\n    public String getCustomerEmail() {\n        return customerEmail;\n    }\n\n}<\/code><\/pre>\n<p>Diese Datei definiert ein <a href=\"https:\/\/en.wikipedia.org\/wiki\/Plain_old_Java_object\" target=\"_blank\" rel=\"noopener noreferrer\">POJO<\/a>, das den Kundennamen, die E-Mail und die Liste der Artikel enth\u00e4lt, mit denen der Kunde auscheckt.<\/p>\n<p>Implementiere schlie\u00dflich die Methode <code>CustomerUtil.findOrCreateCustomer()<\/code>, um das Kundenobjekt in Stripe zu erstellen, falls es noch nicht existiert. Dazu erstellst du eine Datei mit dem Namen <code>CustomerUtil<\/code> und f\u00fcgst den folgenden Code hinzu:<\/p>\n<pre><code class=\"language-java\">package com.kinsta.stripejava.backend;\n\nimport com.stripe.exception.StripeException;\nimport com.stripe.model.Customer;\nimport com.stripe.model.CustomerSearchResult;\nimport com.stripe.param.CustomerCreateParams;\nimport com.stripe.param.CustomerSearchParams;\n\npublic class CustomerUtil {\n\n    public static Customer findCustomerByEmail(String email) throws StripeException {\n        CustomerSearchParams params =\n                CustomerSearchParams\n                        .builder()\n                        .setQuery(\"email:'\" + email + \"'\")\n                        .build();\n\n        CustomerSearchResult result = Customer.search(params);\n\n        return result.getData().size() &gt; 0 ? result.getData().get(0) : null;\n    }\n\n    public static Customer findOrCreateCustomer(String email, String name) throws StripeException {\n        CustomerSearchParams params =\n                CustomerSearchParams\n                        .builder()\n                        .setQuery(\"email:'\" + email + \"'\")\n                        .build();\n\n        CustomerSearchResult result = Customer.search(params);\n\n        Customer customer;\n\n        \/\/ If no existing customer was found, create a new record\n        if (result.getData().size() == 0) {\n\n            CustomerCreateParams customerCreateParams = CustomerCreateParams.builder()\n                    .setName(name)\n                    .setEmail(email)\n                    .build();\n\n            customer = Customer.create(customerCreateParams);\n        } else {\n            customer = result.getData().get(0);\n        }\n\n        return customer;\n    }\n}<\/code><\/pre>\n<p>Diese Klasse enth\u00e4lt au\u00dferdem eine weitere Methode <code>findCustomerByEmail<\/code>, mit der du Kunden in Stripe anhand ihrer E-Mail-Adressen suchen kannst. Die <a href=\"https:\/\/example.com\/docs\/api\/customers\/search\" target=\"_blank\" rel=\"noopener noreferrer\">Kundensuche-API<\/a> wird verwendet, um die Kundendatens\u00e4tze in der Stripe-Datenbank zu suchen, und die <a href=\"https:\/\/example.com\/docs\/api\/customers\/create\" target=\"_blank\" rel=\"noopener noreferrer\">Kundenerstellungs-API<\/a> wird verwendet, um die Kundendatens\u00e4tze nach Bedarf zu erstellen.<\/p>\n<p>Damit ist die Einrichtung des Backends f\u00fcr den gehosteten Checkout abgeschlossen. Du kannst die Anwendung jetzt testen, indem du die Frontend- und die Backend-App in ihren IDEs oder auf separaten Terminals ausf\u00fchrst. So w\u00fcrde der erfolgreiche Ablauf aussehen:<\/p>\n<figure id=\"attachment_163058\" aria-describedby=\"caption-attachment-163058\" style=\"width: 1912px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163058 size-full\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/successful-hosted-checkout-flow.gif\" alt=\"Ein Benutzerablauf, der zeigt, wie ein erfolgreicher Checkout \u00fcber die gehostete Stripe-Seite aussieht.\" width=\"1912\" height=\"1062\"><figcaption id=\"caption-attachment-163058\" class=\"wp-caption-text\">Ein erfolgreicher Hosted Checkout Flow<\/figcaption><\/figure>\n<p>Wenn du Stripe-Integrationen testest, kannst du immer die folgenden Kartendaten verwenden, um Kartentransaktionen zu simulieren:<\/p>\n<p><strong>Kartennummer<\/strong>: 4111 1111 1111 1111<br \/>\n<strong>G\u00fcltigkeitsmonat und -jahr<\/strong>: 12 \/ 25<br \/>\n<strong>CVV<\/strong>: Beliebige dreistellige Zahl<br \/>\n<strong>Name auf der Karte<\/strong>: Beliebiger Name<\/p>\n<p>Wenn du dich daf\u00fcr entscheidest, die Transaktion zu stornieren, anstatt zu bezahlen, sieht der Ablauf folgenderma\u00dfen aus:<\/p>\n<figure id=\"attachment_163059\" aria-describedby=\"caption-attachment-163059\" style=\"width: 1912px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163059 size-full\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/failed-hosted-checkout-flow.gif\" alt=\"Ein User Flow, der zeigt, wie ein fehlgeschlagener Checkout \u00fcber die gehostete Stripe-Seite aussieht.\" width=\"1912\" height=\"1062\"><figcaption id=\"caption-attachment-163059\" class=\"wp-caption-text\">Ein fehlgeschlagener Hosted Checkout Flow<\/figcaption><\/figure>\n<p>Damit ist die Einrichtung einer von Stripe gehosteten Kaufabwicklung in deiner Anwendung abgeschlossen. In den <a href=\"https:\/\/example.com\/docs\/payments\/checkout\/how-checkout-works\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe-Dokumenten<\/a> erf\u00e4hrst du mehr dar\u00fcber, wie du deine Kassenseite anpassen kannst, wie du weitere Details vom Kunden sammelst und vieles mehr.<\/p>\n<h3>Integrierter Checkout<\/h3>\n<p>Eine integrierte Kaufabwicklung bedeutet, dass du einen Zahlungsablauf erstellst, der deine Nutzer nicht au\u00dferhalb deiner Anwendung weiterleitet (wie bei der gehosteten Kaufabwicklung), sondern das Zahlungsformular direkt in deiner Anwendung anzeigt.<\/p>\n<p>Eine integrierte Kaufabwicklung bedeutet, dass du die Zahlungsdaten deiner Kunden verarbeiten musst, was sensible Informationen wie Kreditkartennummern, Google Pay ID usw. beinhaltet. Nicht alle Anwendung sind f\u00fcr den sicheren Umgang mit diesen Daten ausgelegt.<\/p>\n<p>Um dich von der Einhaltung von Standards wie PCI-DSS zu entlasten, bietet Stripe <a href=\"https:\/\/example.com\/payments\/elements\" target=\"_blank\" rel=\"noopener noreferrer\">Elemente<\/a> an, die du in der Anwendung verwenden kannst, um Zahlungsdaten zu sammeln, w\u00e4hrend Stripe die Sicherheit verwaltet und die Zahlungen sicher abwickelt.<\/p>\n<h4>Das Frontend erstellen<\/h4>\n<p>Installiere zun\u00e4chst das <a href=\"https:\/\/www.npmjs.com\/package\/@stripe\/react-stripe-js\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe React SDK<\/a> in deiner Frontend-Anwendung, um auf die Stripe-Elemente zuzugreifen, indem du den folgenden Befehl in deinem Frontend-Verzeichnis ausf\u00fchrst:<\/p>\n<pre><code class=\"language-bash\">npm i @stripe\/react-stripe-js @stripe\/stripe-js<\/code><\/pre>\n<p>Als N\u00e4chstes erstellst du eine neue Datei namens <strong>IntegratedCheckout.tsx<\/strong> in deinem <strong>frontend\/src\/routes-Verzeichnis<\/strong> und speicherst den folgenden Code darin:<\/p>\n<pre><code class=\"language-js\">import {Button, Center, Heading, Input, VStack} from \"@chakra-ui\/react\";\nimport {useEffect, useState} from \"react\";\nimport CartItem, {ItemData} from \"..\/components\/CartItem.tsx\";\nimport TotalFooter from \"..\/components\/TotalFooter.tsx\";\nimport {Products} from '..\/data.ts'\nimport {Elements, PaymentElement, useElements, useStripe} from '@stripe\/react-stripe-js';\nimport {loadStripe, Stripe} from '@stripe\/stripe-js';\n\nfunction IntegratedCheckout() {\n\n    const [items] = useState&lt;ItemData[]&gt;(Products)\n    const [transactionClientSecret, setTransactionClientSecret] = useState(\"\")\n    const [stripePromise, setStripePromise] = useState&lt;Promise&lt;Stripe | null&gt; | null&gt;(null)\n    const [name, setName] = useState(\"\")\n    const [email, setEmail] = useState(\"\")\n    const onCustomerNameChange = (ev: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {\n        setName(ev.target.value)\n    }\n\n    const onCustomerEmailChange = (ev: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {\n        setEmail(ev.target.value)\n    }\n\n    useEffect(() =&gt; {\n        \/\/ Make sure to call `loadStripe` outside of a component\u2019s render to avoid\n        \/\/ recreating the `Stripe` object on every render.\n        setStripePromise(loadStripe(process.env.VITE_STRIPE_API_KEY || \"\"));\n\n    }, [])\n\n    const createTransactionSecret = () =&gt; {\n        fetch(process.env.VITE_SERVER_BASE_URL + \"\/checkout\/integrated\", {\n            method: \"POST\",\n            headers: {'Content-Type': 'application\/json'},\n            body: JSON.stringify({\n                items: items.map(elem =&gt; ({name: elem.name, id: elem.id})),\n                customerName: name,\n                customerEmail: email,\n            })\n        })\n            .then(r =&gt; r.text())\n            .then(r =&gt; {\n                setTransactionClientSecret(r)\n            })\n    }\n\n    return &lt;&gt;\n        &lt;Center h={'100vh'} color='black'&gt;\n            &lt;VStack spacing='24px'&gt;\n                &lt;Heading&gt;Integrated Checkout Example&lt;\/Heading&gt;\n                {items.map(elem =&gt; {\n                    return &lt;CartItem data={elem} mode={'checkout'}\/&gt;\n                })}\n                &lt;TotalFooter total={30} mode={\"checkout\"}\/&gt;\n\n                &lt;Input variant='filled' placeholder='Customer Name' onChange={onCustomerNameChange}\n                       value={name}\/&gt;\n                &lt;Input variant='filled' placeholder='Customer Email' onChange={onCustomerEmailChange}\n                       value={email}\/&gt;\n                &lt;Button onClick={createTransactionSecret} colorScheme={'green'}&gt;Initiate Payment&lt;\/Button&gt;\n\n                {(transactionClientSecret === \"\" ?\n                    &lt;&gt;&lt;\/&gt;\n                    : &lt;Elements stripe={stripePromise} options={{clientSecret: transactionClientSecret}}&gt;\n                        &lt;CheckoutForm\/&gt;\n                    &lt;\/Elements&gt;)}\n            &lt;\/VStack&gt;\n        &lt;\/Center&gt;\n    &lt;\/&gt;\n}\n\nconst CheckoutForm = () =&gt; {\n\n    const stripe = useStripe();\n    const elements = useElements();\n    const handleSubmit = async (event: React.MouseEvent&lt;HTMLButtonElement&gt;) =&gt; {\n        event.preventDefault();\n\n        if (!stripe || !elements) {\n            return;\n        }\n\n        const result = await stripe.confirmPayment({\n            elements,\n            confirmParams: {\n                return_url: process.env.VITE_CLIENT_BASE_URL + \"\/success\",\n            },\n        });\n\n        if (result.error) {\n            console.log(result.error.message);\n        }\n    };\n\n    return &lt;&gt;\n        &lt;VStack&gt;\n            &lt;PaymentElement\/&gt;\n            &lt;Button colorScheme={'green'} disabled={!stripe} onClick={handleSubmit}&gt;Pay&lt;\/Button&gt;\n        &lt;\/VStack&gt;\n    &lt;\/&gt;\n}\n\nexport default IntegratedCheckout\n<\/code><\/pre>\n<p>Diese Datei definiert zwei Komponenten, <code>IntegratedCheckout<\/code> und <code>CheckoutForm<\/code>. Die <code>CheckoutForm<\/code> definiert ein einfaches Formular mit einer <code>PaymentElement<\/code> von Stripe, die die Zahlungsdaten der Kunden sammelt, und einer Schaltfl\u00e4che <strong>Bezahlen<\/strong>, die eine Zahlungsaufforderung ausl\u00f6st.<\/p>\n<p>Diese Komponente ruft auch die Hooks <code>useStripe()<\/code> und <code>useElements()<\/code> auf, um eine Instanz des Stripe SDK zu erstellen, die du zum Erstellen von Zahlungsanforderungen verwenden kannst. Sobald du auf die Schaltfl\u00e4che <strong>Bezahlen<\/strong>\u00a0klickst, wird die Methode <code>stripe.confirmPayment()<\/code> aus dem Stripe SDK aufgerufen, die die Zahlungsdaten des Nutzers aus der Instanz der Elemente sammelt und an das Stripe-Backend sendet, zusammen mit einer Erfolgs-URL, zu der du weitergeleitet wirst, wenn die Transaktion erfolgreich war.<\/p>\n<p>Das Checkout-Formular wurde vom Rest der Seite getrennt, weil die Hooks <code>useStripe()<\/code> und <code>useElements()<\/code> aus dem Kontext eines <code>Elements<\/code> Anbieters aufgerufen werden m\u00fcssen, was in der R\u00fcckgabeanweisung von <code>IntegratedCheckout<\/code> geschieht. Wenn du die Stripe-Hook-Aufrufe direkt in die Komponente <code>IntegratedCheckout<\/code> verschieben w\u00fcrdest, l\u00e4gen sie au\u00dferhalb des Bereichs des <code>Elements<\/code> Anbieters und w\u00fcrden daher nicht funktionieren.<\/p>\n<p>Die Komponente <code>IntegratedCheckout<\/code> verwendet die Komponenten <code>CartItem<\/code> und <code>TotalFooter<\/code>, um die Artikel im Warenkorb und den Gesamtbetrag anzuzeigen. Sie zeigt au\u00dferdem zwei Eingabefelder f\u00fcr die Kundendaten und eine Schaltfl\u00e4che <strong>Zahlung einleiten<\/strong>\u00a0an, die eine Anfrage an den Java-Backend-Server sendet, um den geheimen Kundenschl\u00fcssel anhand der Kunden- und Warenkorbdaten zu erstellen. Sobald der geheime Kundenschl\u00fcssel eingegangen ist, wird die Seite <code>CheckoutForm<\/code> angezeigt, die die Zahlungsdaten des Kunden erfasst.<\/p>\n<p>Au\u00dferdem wird <code>useEffect<\/code> verwendet, um die Methode <code>loadStripe<\/code> aufzurufen. Dieser Effekt wird nur einmal ausgef\u00fchrt, wenn die Komponente gerendert wird, damit das Stripe SDK nicht mehrmals geladen wird, wenn die internen Zust\u00e4nde der Komponente aktualisiert werden.<\/p>\n<p>Um den obigen Code auszuf\u00fchren, musst du au\u00dferdem zwei neue Umgebungsvariablen zu deinem Frontend-Projekt hinzuf\u00fcgen: <code>VITE_STRIPE_API_KEY<\/code> und <code>VITE_CLIENT_BASE_URL<\/code>. Die Variable f\u00fcr den Stripe-API-Schl\u00fcssel enth\u00e4lt den ver\u00f6ffentlichbaren API-Schl\u00fcssel aus dem <a href=\"https:\/\/dashboard.example.com\/test\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe-Dashboard<\/a>, und die Variable f\u00fcr die Client Base URL enth\u00e4lt den Link zur Client-Anwendung (die Frontend-Anwendung selbst), damit sie an das Stripe-SDK weitergegeben werden kann, um Erfolgs- und Fehlerumleitungen zu verarbeiten.<\/p>\n<p>Dazu f\u00fcgst du den folgenden Code in deine <strong>.env-Datei<\/strong> im Frontend-Verzeichnis ein:<\/p>\n<pre><code class=\"language-bash\">VITE_STRIPE_API_KEY=pk_test_xxxxxxxxxx # Your key here\nVITE_CLIENT_BASE_URL=http:\/\/localhost:5173<\/code><\/pre>\n<p>Aktualisiere schlie\u00dflich die <strong>App.tsx-Datei<\/strong>, um die Komponente <code>IntegratedCheckout<\/code> in die <code>\/integrated-checkout<\/code> Route der Frontend-Anwendung einzubinden. F\u00fcge den folgenden Code in das Array ein, das an den Aufruf <code>createBrowserRouter<\/code> in der Komponente <code>App<\/code> \u00fcbergeben wird:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/integrated-checkout',\n            element: (\n                &lt;IntegratedCheckout\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Damit sind die erforderlichen Einstellungen am Frontend abgeschlossen. Als N\u00e4chstes erstellst du eine neue Route auf deinem Backend-Server, die den geheimen Client-Schl\u00fcssel erstellt, der f\u00fcr die integrierten Checkout-Sitzungen in deiner Frontend-Anwendung ben\u00f6tigt wird.<\/p>\n<h4>Aufbau des Backends<\/h4>\n<p>Um sicherzustellen, dass die Frontend-Integration nicht von Angreifern missbraucht wird (da der Frontend-Code leichter zu knacken ist als der Backend-Code), verlangt Stripe von dir, dass du ein eindeutiges Kundengeheimnis auf deinem Backend-Server generierst und jede integrierte Zahlungsanforderung mit dem im Backend generierten Kundengeheimnis verifizierst, um sicherzustellen, dass es tats\u00e4chlich deine Anwendung ist, die versucht, Zahlungen einzuziehen. Dazu musst du im Backend eine weitere Route einrichten, die anhand der Kunden- und Warenkorbinformationen Kundengeheimnisse erstellt.<\/p>\n<p>Um den Schl\u00fcssel f\u00fcr das Kundengeheimnis auf deinem Server zu erstellen, erstelle eine neue Methode in deiner Klasse <code>PaymentController<\/code> mit dem Namen <code>integratedCheckout<\/code> und speichere den folgenden Code darin:<\/p>\n<pre><code class=\"language-java\">@PostMapping(\"\/checkout\/integrated\")\n    String integratedCheckout(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        Stripe.apiKey = STRIPE_API_KEY;\n\n        \/\/ Start by finding existing customer or creating a new one if needed\n        Customer customer = CustomerUtil.findOrCreateCustomer(requestDTO.getCustomerEmail(), requestDTO.getCustomerName());\n\n        \/\/ Create a PaymentIntent and send it's client secret to the client\n        PaymentIntentCreateParams params =\n                PaymentIntentCreateParams.builder()\n                       .setAmount(Long.parseLong(calculateOrderAmount(requestDTO.getItems())))\n                        .setCurrency(\"usd\")\n                        .setCustomer(customer.getId())\n                        .setAutomaticPaymentMethods(\n                                PaymentIntentCreateParams.AutomaticPaymentMethods\n                                        .builder()\n                                        .setEnabled(true)\n                                        .build()\n                        )\n                        .build();\n\n        PaymentIntent paymentIntent = PaymentIntent.create(params);\n\n        \/\/ Send the client secret from the payment intent to the client\n        return paymentIntent.getClientSecret();\n    }<\/code><\/pre>\n<p>\u00c4hnlich wie die Kassensitzung mit Hilfe einer Builder-Klasse erstellt wurde, die die Konfiguration f\u00fcr die Zahlungsanforderung \u00fcbernimmt, musst du f\u00fcr den integrierten Checkout-Flow eine Zahlungssitzung mit dem Betrag, der W\u00e4hrung und den Zahlungsarten erstellen. Anders als bei der Kassensitzung kannst du einer Zahlungssitzung keine <a href=\"https:\/\/example.com\/docs\/api\/invoices\/line_item\" target=\"_blank\" rel=\"noopener noreferrer\">Einzelposten<\/a> zuordnen, es sei denn, du erstellst eine Rechnung, was du in einem sp\u00e4teren Abschnitt des Leitfadens lernen wirst.<\/p>\n<p>Da du die Einzelposten nicht an den Checkout Session Builder weitergibst, musst du den Gesamtbetrag f\u00fcr die Artikel im Warenkorb manuell berechnen und den Betrag an das Stripe Backend senden. Verwende dein <code>ProductDAO<\/code>, um die Preise f\u00fcr jedes Produkt im Warenkorb zu finden und hinzuzuf\u00fcgen.<\/p>\n<p>Dazu definierst du eine neue Methode <code>calculateOrderAmount<\/code> und f\u00fcgst den folgenden Code darin ein:<\/p>\n<pre><code class=\"language-java\">     static String calculateOrderAmount(Product[] items) {\n        long total = 0L;\n\n        for (Product item: items) {\n            \/\/ Look up the application database to find the prices for the products in the given list\n            total += ProductDAO.getProduct(item.getId()).getDefaultPriceObject().getUnitAmountDecimal().floatValue();\n        }\n        return String.valueOf(total);\n    }\n<\/code><\/pre>\n<p>Das sollte ausreichen, um den integrierten Checkout-Flow sowohl auf dem Frontend als auch auf dem Backend einzurichten. Du kannst die Entwicklungsserver f\u00fcr den Server und den Client neu starten und den neuen integrierten Checkout-Flow in der Frontend-Anwendung ausprobieren. Hier siehst du, wie der integrierte Ablauf aussehen wird:<\/p>\n<figure id=\"attachment_163060\" aria-describedby=\"caption-attachment-163060\" style=\"width: 1912px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163060 size-full\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/integrated-checkout-flow.gif\" alt=\"Ein User Flow, der zeigt, wie ein erfolgreicher integrierter Checkout mit der Stripe-Integration aussieht.\" width=\"1912\" height=\"1062\"><figcaption id=\"caption-attachment-163060\" class=\"wp-caption-text\">Ein integrierter Checkout-Flow<\/figcaption><\/figure>\n<p>Damit ist der grundlegende integrierte Checkout-Flow in deiner App fertiggestellt. Du kannst nun die Stripe-Dokumentation weiter durchforsten, um <a href=\"https:\/\/example.com\/docs\/payments\/customize-payment-element\" target=\"_blank\" rel=\"noopener noreferrer\">die Zahlungsmethoden anzupassen<\/a> oder weitere Komponenten zu integrieren, die dir bei anderen Vorg\u00e4ngen helfen, z. B. bei der <a href=\"https:\/\/example.com\/docs\/elements\/address-element\" target=\"_blank\" rel=\"noopener noreferrer\">Adresserfassung<\/a>, bei <a href=\"https:\/\/example.com\/docs\/payments\/elements\/link-authentication-element\" target=\"_blank\" rel=\"noopener noreferrer\">Zahlungsanforderungen<\/a>, bei der <a href=\"https:\/\/example.com\/docs\/stripe-js\/elements\/payment-request-button\" target=\"_blank\" rel=\"noopener noreferrer\">Integration von Links<\/a> und vielem mehr!<\/p>\n<h2>Einrichten von Abonnements f\u00fcr wiederkehrende Dienste<\/h2>\n<p>Ein g\u00e4ngiges Angebot von Online-Shops ist heutzutage ein Abonnement. Egal, ob du einen Marktplatz f\u00fcr Dienstleistungen aufbaust oder ein digitales Produkt regelm\u00e4\u00dfig anbietest, ein Abonnement ist die perfekte L\u00f6sung, um deinen Kunden f\u00fcr eine geringe Geb\u00fchr im Vergleich zu einem einmaligen Kauf regelm\u00e4\u00dfig Zugang zu deinem Service zu geben.<\/p>\n<p>Mit Stripe kannst du ganz einfach Abonnements einrichten und k\u00fcndigen. Du kannst auch kostenlose Testversionen als Teil deines Abonnements anbieten, damit die Nutzer dein Angebot ausprobieren k\u00f6nnen, bevor sie sich binden.<\/p>\n<h3>Ein neues Abonnement einrichten<\/h3>\n<p>Das Einrichten eines neuen Abonnements ist mit dem gehosteten Kassenablauf ganz einfach. Du musst nur ein paar Parameter bei der Erstellung der Checkout-Anfrage \u00e4ndern und eine neue Seite erstellen (indem du die bestehenden Komponenten wiederverwendest), um eine Checkout-Seite f\u00fcr ein neues Abonnement anzuzeigen. Erstelle zun\u00e4chst eine Datei <strong>NewSubscription.tsx<\/strong> im Ordner mit <strong>den Frontend-Komponenten<\/strong>. F\u00fcge den folgenden Code darin ein:<\/p>\n<pre><code class=\"language-js\">import {Center, Heading, VStack} from \"@chakra-ui\/react\";\nimport {useState} from \"react\";\nimport CartItem, {ItemData} from \"..\/components\/CartItem.tsx\";\nimport TotalFooter from \"..\/components\/TotalFooter.tsx\";\nimport CustomerDetails from \"..\/components\/CustomerDetails.tsx\";\nimport {Subscriptions} from \"..\/data.ts\";\n\nfunction NewSubscription() {\n    const [items] = useState&lt;ItemData[]&gt;(Subscriptions)\n    return &lt;&gt;\n        &lt;Center h={'100vh'} color='black'&gt;\n            &lt;VStack spacing='24px'&gt;\n                &lt;Heading&gt;New Subscription Example&lt;\/Heading&gt;\n                {items.map(elem =&gt; {\n                    return &lt;CartItem data={elem} mode={'subscription'}\/&gt;\n                })}\n                &lt;TotalFooter total={4.99} mode={\"subscription\"}\/&gt;\n                &lt;CustomerDetails data={items} endpoint={\"\/subscriptions\/new\"} \/&gt;\n            &lt;\/VStack&gt;\n        &lt;\/Center&gt;\n    &lt;\/&gt;\n}\n\nexport default NewSubscription\n<\/code><\/pre>\n<p>Im obigen Code werden die Daten des Warenkorbs aus der Datei <strong>data.ts<\/strong> \u00fcbernommen und enthalten nur einen Artikel, um den Prozess zu vereinfachen. In der Praxis kann es vorkommen, dass du mehrere Artikel in einem Abonnementauftrag hast.<\/p>\n<p>Um diese Komponente auf der richtigen Route darzustellen, f\u00fcge den folgenden Code in das Array ein, das an den <code>createBrowserRouter<\/code> -Aufruf in der Komponente <strong>App.tsx<\/strong> \u00fcbergeben wird:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/new-subscription',\n            element: (\n                &lt;NewSubscription\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Damit ist die Einrichtung auf dem Frontend abgeschlossen. Im Backend erstellst du eine neue Route <code>\/subscription\/new<\/code>, um eine neue gehostete Checkout-Sitzung f\u00fcr ein Abonnementprodukt zu erstellen. Erstelle eine Methode <code>newSubscription<\/code> im Verzeichnis <strong>backend\/src\/main\/java\/com\/kinsta\/stripejava\/backend<\/strong> und speichere den folgenden Code darin:<\/p>\n<pre><code class=\"language-java\">@PostMapping(\"\/subscriptions\/new\")\n    String newSubscription(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        Stripe.apiKey = STRIPE_API_KEY;\n\n        String clientBaseURL = System.getenv().get(\"CLIENT_BASE_URL\");\n\n        \/\/ Start by finding existing customer record from Stripe or creating a new one if needed\n        Customer customer = CustomerUtil.findOrCreateCustomer(requestDTO.getCustomerEmail(), requestDTO.getCustomerName());\n\n        \/\/ Next, create a checkout session by adding the details of the checkout\n        SessionCreateParams.Builder paramsBuilder =\n                SessionCreateParams.builder()\n                        \/\/ For subscriptions, you need to set the mode as subscription\n                        .setMode(SessionCreateParams.Mode.SUBSCRIPTION)\n                        .setCustomer(customer.getId())\n                        .setSuccessUrl(clientBaseURL + \"\/success?session_id={CHECKOUT_SESSION_ID}\")\n                        .setCancelUrl(clientBaseURL + \"\/failure\");\n\n        for (Product product : requestDTO.getItems()) {\n            paramsBuilder.addLineItem(\n                    SessionCreateParams.LineItem.builder()\n                            .setQuantity(1L)\n                            .setPriceData(\n                                    PriceData.builder()\n                                            .setProductData(\n                                                    PriceData.ProductData.builder()\n                                                            .putMetadata(\"app_id\", product.getId())\n                                                            .setName(product.getName())\n                                                            .build()\n                                            )\n                                            .setCurrency(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getCurrency())\n                                            .setUnitAmountDecimal(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getUnitAmountDecimal())\n                                            \/\/ For subscriptions, you need to provide the details on how often they would recur\n                                            .setRecurring(PriceData.Recurring.builder().setInterval(PriceData.Recurring.Interval.MONTH).build())\n                                            .build())\n                            .build());\n        }\n\n        Session session = Session.create(paramsBuilder.build());\n\n        return session.getUrl();\n    }<\/code><\/pre>\n<p>Der Code in dieser Methode ist dem Code in der Methode <code>hostedCheckout<\/code> sehr \u00e4hnlich, nur dass der Modus f\u00fcr die Erstellung der Sitzung Abonnement statt Produkt ist und vor der Erstellung der Sitzung ein Wert f\u00fcr das Wiederholungsintervall f\u00fcr das Abonnement festgelegt wird.<\/p>\n<p>Dadurch wird Stripe angewiesen, diese Kasse als Abonnement-Kasse zu behandeln und nicht als Einmalzahlung. \u00c4hnlich wie die Methode <code>hostedCheckout<\/code> gibt auch diese Methode die URL der gehosteten Checkout-Seite als HTTP-Antwort an den Kunden zur\u00fcck. Der Client wird auf die empfangene URL umgeleitet, damit der Kunde die Zahlung abschlie\u00dfen kann.<\/p>\n<p>Du kannst die Entwicklungsserver sowohl f\u00fcr den Client als auch f\u00fcr den Server neu starten und die neue Abo-Seite in Aktion sehen. So sieht sie aus:<\/p>\n<figure id=\"attachment_163061\" aria-describedby=\"caption-attachment-163061\" style=\"width: 1914px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163061 size-full\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/hosted-subscription-checkout-flow.gif\" alt=\"Ein User Flow, der zeigt, wie eine erfolgreiche Abo-Kaufabwicklung \u00fcber die von Stripe gehostete Seite aussieht.\" width=\"1914\" height=\"976\"><figcaption id=\"caption-attachment-163061\" class=\"wp-caption-text\">Eine gehostete Abonnement-Kaufabwicklung<\/figcaption><\/figure>\n<h3>Ein bestehendes Abonnement k\u00fcndigen<\/h3>\n<p>Nachdem du nun wei\u00dft, wie du neue Abonnements erstellen kannst, wollen wir nun lernen, wie du deinen Kunden die M\u00f6glichkeit gibst, bestehende Abonnements zu k\u00fcndigen. Da die Demo-Anwendung in diesem Tutorial keine Authentifizierungseinstellungen enth\u00e4lt, kannst du ein Formular verwenden, in das der Kunde seine E-Mail-Adresse eingeben kann, um seine Abonnements zu \u00fcberpr\u00fcfen.<\/p>\n<p>Hierf\u00fcr musst du Folgendes tun:<\/p>\n<ol>\n<li>Aktualisiere die Komponente <code>CartItem<\/code> so, dass sie auf der Seite &#8222;Abonnements k\u00fcndigen&#8220; eine Schaltfl\u00e4che f\u00fcr die K\u00fcndigung anzeigt.<\/li>\n<li>Erstelle eine <code>CancelSubscription<\/code> Komponente, die zun\u00e4chst ein Eingabefeld und eine Schaltfl\u00e4che anzeigt, \u00fcber die der Kunde anhand seiner E-Mail-Adresse nach Abonnements suchen kann, und dann eine Liste der Abonnements mit der aktualisierten <code>CartItem<\/code> Komponente anzeigt.<\/li>\n<li>Erstelle eine neue Methode im Backend-Server, die anhand der E-Mail-Adresse des Kunden nach Abonnements im Stripe-Backend suchen kann.<\/li>\n<li>Erstelle eine neue Methode im Backend-Server, die ein Abonnement anhand der \u00fcbergebenen Abonnement-ID k\u00fcndigen kann.<\/li>\n<\/ol>\n<p>Beginne damit, die Komponente <code>CartItem<\/code> zu aktualisieren, damit sie wie folgt aussieht:<\/p>\n<pre><code class=\"language-js\">\/\/ Existing imports here\n\nfunction CartItem(props: CartItemProps) {\n\n    \/\/ Add this hook call and the cancelSubscription method to cancel the selected subscription\n    const toast = useToast()\n    const cancelSubscription = () =&gt; {\n\n        fetch(process.env.VITE_SERVER_BASE_URL + \"\/subscriptions\/cancel\", {\n            method: \"POST\",\n            headers: {'Content-Type': 'application\/json'},\n            body: JSON.stringify({\n                subscriptionId: props.data.stripeSubscriptionData?.subscriptionId\n            })\n        })\n            .then(r =&gt; r.text())\n            .then(() =&gt; {\n                toast({\n                    title: 'Subscription cancelled.',\n                    description: \"We've cancelled your subscription for you.\",\n                    status: 'success',\n                    duration: 9000,\n                    isClosable: true,\n                })\n\n                if (props.onCancelled)\n                    props.onCancelled()\n            })\n    }\n\n    return &lt;Card direction={{base: 'column', sm: 'row'}}\n                 overflow='hidden'\n                 width={'xl'}\n                 variant='outline'&gt;\n        &lt;Image\n            objectFit='cover'\n            maxW={{base: '100%', sm: '200px'}}\n            src={props.data.image}\n        \/&gt;\n        &lt;Stack mt='6' spacing='3'&gt;\n            &lt;CardBody&gt;\n                &lt;VStack spacing={'3'} alignItems={\"flex-start\"}&gt;\n                    &lt;Heading size='md'&gt;{props.data.name}&lt;\/Heading&gt;\n                    &lt;VStack spacing={'1'} alignItems={\"flex-start\"}&gt;\n                        &lt;Text&gt;\n                            {props.data.description}\n                        &lt;\/Text&gt;\n                        {(props.mode === \"checkout\" ? &lt;Text&gt;\n                            {\"Quantity: \" + props.data.quantity}\n                        &lt;\/Text&gt; : &lt;&gt;&lt;\/&gt;)}\n                    &lt;\/VStack&gt;\n\n                    {\/* &lt;----------------------- Add this block ----------------------&gt; *\/}\n                    {(props.mode === \"subscription\" && props.data.stripeSubscriptionData ?\n                        &lt;VStack spacing={'1'} alignItems={\"flex-start\"}&gt;\n                            &lt;Text&gt;\n                                {\"Next Payment Date: \" + props.data.stripeSubscriptionData.nextPaymentDate}\n                            &lt;\/Text&gt;\n                            &lt;Text&gt;\n                                {\"Subscribed On: \" + props.data.stripeSubscriptionData.subscribedOn}\n                            &lt;\/Text&gt;\n                            {(props.data.stripeSubscriptionData.trialEndsOn ? &lt;Text&gt;\n                                {\"Free Trial Running Until: \" + props.data.stripeSubscriptionData.trialEndsOn}\n                            &lt;\/Text&gt; : &lt;&gt;&lt;\/&gt;)}\n                        &lt;\/VStack&gt; : &lt;&gt;&lt;\/&gt;)}\n                &lt;\/VStack&gt;\n\n            &lt;\/CardBody&gt;\n\n            &lt;CardFooter&gt;\n                &lt;VStack alignItems={'flex-start'}&gt;\n                    &lt;Text color='blue.600' fontSize='2xl'&gt;\n                        {\"$\" + props.data.price}\n                    &lt;\/Text&gt;\n                    {\/* &lt;----------------------- Add this block ----------------------&gt; *\/}\n                    {(props.data.stripeSubscriptionData ?\n                        &lt;Button colorScheme={'red'} onClick={cancelSubscription}&gt;Cancel Subscription&lt;\/Button&gt;\n                        : &lt;&gt;&lt;\/&gt;)}\n                &lt;\/VStack&gt;\n            &lt;\/CardFooter&gt;\n        &lt;\/Stack&gt;\n    &lt;\/Card&gt;\n}\n\n\/\/ Existing types here\n\nexport default CartItem\n<\/code><\/pre>\n<p>Als N\u00e4chstes erstellst du eine Komponente <strong>CancelSubscription.tsx<\/strong> im <strong>Routenverzeichnis<\/strong> deines Frontends und speicherst den folgenden Code darin:<\/p>\n<pre><code class=\"language-js\">import {Button, Center, Heading, Input, VStack} from \"@chakra-ui\/react\";\nimport {useState} from \"react\";\nimport CartItem, {ItemData, ServerSubscriptionsResponseType} from \"..\/components\/CartItem.tsx\";\nimport {Subscriptions} from \"..\/data.ts\";\n\nfunction CancelSubscription() {\n    const [email, setEmail] = useState(\"\")\n    const [subscriptions, setSubscriptions] = useState&lt;ItemData[]&gt;([])\n\n    const onCustomerEmailChange = (ev: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {\n        setEmail(ev.target.value)\n    }\n\n    const listSubscriptions = () =&gt; {\n\n        fetch(process.env.VITE_SERVER_BASE_URL + \"\/subscriptions\/list\", {\n            method: \"POST\",\n            headers: {'Content-Type': 'application\/json'},\n            body: JSON.stringify({\n                customerEmail: email,\n            })\n        })\n            .then(r =&gt; r.json())\n            .then((r: ServerSubscriptionsResponseType[]) =&gt; {\n\n                const subscriptionsList: ItemData[] = []\n\n                r.forEach(subscriptionItem =&gt; {\n\n                    let subscriptionDetails = Subscriptions.find(elem =&gt; elem.id === subscriptionItem.appProductId) || undefined\n\n                    if (subscriptionDetails) {\n\n                        subscriptionDetails = {\n                            ...subscriptionDetails,\n                            price: Number.parseInt(subscriptionItem.price) \/ 100,\n                            stripeSubscriptionData: subscriptionItem,\n                        }\n\n                        subscriptionsList.push(subscriptionDetails)\n                    } else {\n                        console.log(\"Item not found!\")\n                    }\n                })\n\n                setSubscriptions(subscriptionsList)\n            })\n\n    }\n\n    const removeSubscription = (id: string | undefined) =&gt; {\n        const newSubscriptionsList = subscriptions.filter(elem =&gt; (elem.stripeSubscriptionData?.subscriptionId !== id))\n        setSubscriptions(newSubscriptionsList)\n    }\n\n    return &lt;&gt;\n        &lt;Center h={'100vh'} color='black'&gt;\n            &lt;VStack spacing={3} width={'xl'}&gt;\n                &lt;Heading&gt;Cancel Subscription Example&lt;\/Heading&gt;\n                {(subscriptions.length === 0 ? &lt;&gt;\n                    &lt;Input variant='filled' placeholder='Customer Email' onChange={onCustomerEmailChange}\n                           value={email}\/&gt;\n                    &lt;Button onClick={listSubscriptions} colorScheme={'green'}&gt;List Subscriptions&lt;\/Button&gt;\n                &lt;\/&gt; : &lt;&gt;&lt;\/&gt;)}\n                {subscriptions.map(elem =&gt; {\n                    return &lt;CartItem data={elem} mode={'subscription'} onCancelled={() =&gt; removeSubscription(elem.stripeSubscriptionData?.subscriptionId)}\/&gt;\n                })}\n            &lt;\/VStack&gt;\n        &lt;\/Center&gt;\n    &lt;\/&gt;\n}\n\nexport default CancelSubscription\n<\/code><\/pre>\n<p>Diese Komponente zeigt ein Eingabefeld und eine Schaltfl\u00e4che an, in die Kunden ihre E-Mail eingeben k\u00f6nnen, um nach Abonnements zu suchen. Wenn Abonnements gefunden werden, werden das Eingabefeld und die Schaltfl\u00e4che ausgeblendet und eine Liste der Abonnements wird auf dem Bildschirm angezeigt. F\u00fcr jedes Abonnement \u00fcbergibt die Komponente eine <code>removeSubscription<\/code> Methode, die den Java-Backend-Server auffordert, das Abonnement im Stripe-Backend zu k\u00fcndigen.<\/p>\n<p>Um die Komponente mit der <code>\/cancel-subscription<\/code> -Route deiner Frontend-Anwendung zu verbinden, f\u00fcge den folgenden Code in das Array ein, das an den <code>createBrowserRouter<\/code> -Aufruf in der Komponente <code>App<\/code> \u00fcbergeben wird:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/cancel-subscription',\n            element: (\n                &lt;CancelSubscription\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Um nach Abonnements auf dem Backend-Server zu suchen, f\u00fcge eine <code>viewSubscriptions<\/code> Methode in der <code>PaymentController<\/code> Klasse deines Backend-Projekts mit folgendem Inhalt hinzu:<\/p>\n<pre><code class=\"language-java\">@PostMapping(\"\/subscriptions\/list\")\n    List&lt;Map&lt;String, String&gt;&gt; viewSubscriptions(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        Stripe.apiKey = STRIPE_API_KEY;\n\n        \/\/ Start by finding existing customer record from Stripe\n        Customer customer = CustomerUtil.findCustomerByEmail(requestDTO.getCustomerEmail());\n\n        \/\/ If no customer record was found, no subscriptions exist either, so return an empty list\n        if (customer == null) {\n            return new ArrayList&lt;&gt;();\n        }\n\n        \/\/ Search for subscriptions for the current customer\n        SubscriptionCollection subscriptions = Subscription.list(\n                SubscriptionListParams.builder()\n                        .setCustomer(customer.getId())\n                        .build());\n\n        List&lt;Map&lt;String, String&gt;&gt; response = new ArrayList&lt;&gt;();\n\n        \/\/ For each subscription record, query its item records and collect in a list of objects to send to the client\n        for (Subscription subscription : subscriptions.getData()) {\n            SubscriptionItemCollection currSubscriptionItems =\n                    SubscriptionItem.list(SubscriptionItemListParams.builder()\n                            .setSubscription(subscription.getId())\n                            .addExpand(\"data.price.product\")\n                            .build());\n\n            for (SubscriptionItem item : currSubscriptionItems.getData()) {\n                HashMap&lt;String, String&gt; subscriptionData = new HashMap&lt;&gt;();\n                subscriptionData.put(\"appProductId\", item.getPrice().getProductObject().getMetadata().get(\"app_id\"));\n                subscriptionData.put(\"subscriptionId\", subscription.getId());\n                subscriptionData.put(\"subscribedOn\", new SimpleDateFormat(\"dd\/MM\/yyyy\").format(new Date(subscription.getStartDate() * 1000)));\n                subscriptionData.put(\"nextPaymentDate\", new SimpleDateFormat(\"dd\/MM\/yyyy\").format(new Date(subscription.getCurrentPeriodEnd() * 1000)));\n                subscriptionData.put(\"price\", item.getPrice().getUnitAmountDecimal().toString());\n\n                if (subscription.getTrialEnd() != null && new Date(subscription.getTrialEnd() * 1000).after(new Date()))\n                    subscriptionData.put(\"trialEndsOn\", new SimpleDateFormat(\"dd\/MM\/yyyy\").format(new Date(subscription.getTrialEnd() * 1000)));\n                response.add(subscriptionData);\n            }\n\n        }\n\n        return response;\n    }<\/code><\/pre>\n<p>Die obige Methode findet zun\u00e4chst das Kundenobjekt f\u00fcr den angegebenen Nutzer in Stripe. Dann sucht sie nach aktiven Abonnements des Kunden. Sobald sie die Liste der Abonnements erhalten hat, extrahiert sie die Artikel daraus und sucht die entsprechenden Produkte in der Produktdatenbank der Anwendung, um sie an das Frontend zu senden. Das ist wichtig, denn die ID, mit der das Frontend jedes Produkt in der Anwendungs-Datenbank identifiziert, kann mit der in Stripe gespeicherten Produkt-ID \u00fcbereinstimmen oder auch nicht.<\/p>\n<p>Zum Schluss erstellst du eine <code>cancelSubscription&lt;\/code method in the <code>PaymentController<\/code> class and paste the code below to delete a subscription based on the subscription ID passed.<\/code><\/p>\n<pre><code class=\"language-java\">@PostMapping(\"\/subscriptions\/cancel\")\n    String cancelSubscription(@RequestBody RequestDTO requestDTO) throws StripeException {\n        Stripe.apiKey = STRIPE_API_KEY;\n\n        Subscription subscription =\n                Subscription.retrieve(\n                        requestDTO.getSubscriptionId()\n                );\n\n        Subscription deletedSubscription =\n                subscription.cancel();\n\n        return deletedSubscription.getStatus();\n    }<\/code><\/pre>\n<p>Diese Methode ruft das Abonnement-Objekt von Stripe ab, ruft die Cancel-Methode daf\u00fcr auf und gibt dann den Abonnement-Status an den Kunden zur\u00fcck. Um diese Methode ausf\u00fchren zu k\u00f6nnen, musst du dein DTO-Objekt aktualisieren und das Feld <code>subscriptionId<\/code> hinzuf\u00fcgen. Dazu f\u00fcgst du das folgende Feld und die folgende Methode in der Klasse <code>RequestDTO<\/code> hinzu:<\/p>\n<pre><code class=\"language-java\">package com.kinsta.stripejava.backend;\n\nimport com.stripe.model.Product;\n\npublic class RequestDTO {\n    \/\/ \u2026 other fields \u2026\n\n    \/\/ Add this\n    String subscriptionId;\n\n    \/\/ \u2026 other getters \u2026\n\n    \/\/ Add this\n    public String getSubscriptionId() {\n        return subscriptionId;\n    }\n\n}<\/code><\/pre>\n<p>Sobald du dies hinzugef\u00fcgt hast, kannst du den Entwicklungsserver sowohl f\u00fcr das Backend als auch f\u00fcr die Frontend-Anwendung erneut starten und den Abbruchvorgang in Aktion sehen:<\/p>\n<figure id=\"attachment_163062\" aria-describedby=\"caption-attachment-163062\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163062 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/subscription-cancel-flow-1024x522.gif\" alt=\"Ein User Flow, der zeigt, wie eine erfolgreiche Abo-K\u00fcndigung \u00fcber die von Stripe gehostete Seite aussieht.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163062\" class=\"wp-caption-text\">Ein Abo-K\u00fcndigungsfluss<\/figcaption><\/figure>\n<h3>Einrichten von kostenlosen Testversionen f\u00fcr Abonnements mit Nullwerttransaktionen<\/h3>\n<p>Die meisten modernen Abonnements bieten eine kurze kostenlose Testphase an, bevor sie dem Nutzer in Rechnung gestellt werden. So k\u00f6nnen die Nutzer das Produkt oder die Dienstleistung ausprobieren, ohne daf\u00fcr zu investieren. Es ist jedoch ratsam, die Zahlungsdaten des Kunden zu speichern, wenn er sich f\u00fcr die kostenlose Testphase anmeldet, damit du ihn nach Ablauf der Testphase einfach abrechnen kannst.<\/p>\n<p>Stripe vereinfacht die Erstellung solcher Abonnements erheblich. Um zu beginnen, erstelle eine neue Komponente im Verzeichnis <strong>Frontend\/Routen<\/strong> mit dem Namen <strong>SubscriptionWithTrial.tsx<\/strong> und f\u00fcge den folgenden Code ein:<\/p>\n<pre><code class=\"language-js\">import {Center, Heading, VStack} from \"@chakra-ui\/react\";\nimport {useState} from \"react\";\nimport CartItem, {ItemData} from \"..\/components\/CartItem.tsx\";\nimport TotalFooter from \"..\/components\/TotalFooter.tsx\";\nimport CustomerDetails from \"..\/components\/CustomerDetails.tsx\";\nimport {Subscriptions} from \"..\/data.ts\";\n\nfunction SubscriptionWithTrial() {\n    const [items] = useState&lt;ItemData[]&gt;(Subscriptions)\n    return &lt;&gt;\n        &lt;Center h={'100vh'} color='black'&gt;\n            &lt;VStack spacing='24px'&gt;\n                &lt;Heading&gt;New Subscription With Trial Example&lt;\/Heading&gt;\n                {items.map(elem =&gt; {\n                    return &lt;CartItem data={elem} mode={'subscription'}\/&gt;\n                })}\n                &lt;TotalFooter total={4.99} mode={\"trial\"}\/&gt;\n                &lt;CustomerDetails data={items} endpoint={\"\/subscriptions\/trial\"}\/&gt;\n            &lt;\/VStack&gt;\n        &lt;\/Center&gt;\n    &lt;\/&gt;\n}\n\nexport default SubscriptionWithTrial\n<\/code><\/pre>\n<p>Diese Komponente verwendet die zuvor erstellten Komponenten wieder. Der Hauptunterschied zwischen dieser Komponente und der Komponente <code>NewSubscription<\/code> besteht darin, dass sie den Modus f\u00fcr <code>TotalFooter<\/code> als <strong>Testversion<\/strong> und nicht als <strong>Abonnement<\/strong> \u00fcbergibt. Dies bewirkt, dass die Komponente <code>TotalFooter<\/code> einen Text anzeigt, der besagt, dass der Kunde die kostenlose Testversion jetzt starten kann, aber nach einem Monat bezahlt werden muss.<\/p>\n<p>Um diese Komponente mit der <code>\/subscription-with-trial<\/code> -Route in deiner Frontend-Anwendung zu verbinden, f\u00fcge den folgenden Code in das Array ein, das dem <code>createBrowserRouter<\/code> -Aufruf in der <code>App<\/code> -Komponente \u00fcbergeben wird:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/subscription-with-trial',\n            element: (\n                &lt;SubscriptionWithTrial\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Um den Checkout-Flow f\u00fcr Abonnements mit <strong>Testversion<\/strong> im Backend zu erstellen, erstelle eine neue Methode namens <code>newSubscriptionWithTrial<\/code> in der Klasse <code>PaymentController<\/code> und f\u00fcge den folgenden Code hinzu:<\/p>\n<pre><code class=\"language-java\">    @PostMapping(\"\/subscriptions\/trial\")\n    String newSubscriptionWithTrial(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        Stripe.apiKey = STRIPE_API_KEY;\n\n        String clientBaseURL = System.getenv().get(\"CLIENT_BASE_URL\");\n\n        \/\/ Start by finding existing customer record from Stripe or creating a new one if needed\n        Customer customer = CustomerUtil.findOrCreateCustomer(requestDTO.getCustomerEmail(), requestDTO.getCustomerName());\n\n        \/\/ Next, create a checkout session by adding the details of the checkout\n        SessionCreateParams.Builder paramsBuilder =\n                SessionCreateParams.builder()\n                        .setMode(SessionCreateParams.Mode.SUBSCRIPTION)\n                        .setCustomer(customer.getId())\n                        .setSuccessUrl(clientBaseURL + \"\/success?session_id={CHECKOUT_SESSION_ID}\")\n                        .setCancelUrl(clientBaseURL + \"\/failure\")\n                        \/\/ For trials, you need to set the trial period in the session creation request\n                        .setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setTrialPeriodDays(30L).build());\n\n        for (Product product : requestDTO.getItems()) {\n            paramsBuilder.addLineItem(\n                    SessionCreateParams.LineItem.builder()\n                            .setQuantity(1L)\n                            .setPriceData(\n                                    PriceData.builder()\n                                            .setProductData(\n                                                    PriceData.ProductData.builder()\n                                                            .putMetadata(\"app_id\", product.getId())\n                                                            .setName(product.getName())\n                                                            .build()\n                                            )\n                                            .setCurrency(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getCurrency())\n                                            .setUnitAmountDecimal(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getUnitAmountDecimal())\n                                            .setRecurring(PriceData.Recurring.builder().setInterval(PriceData.Recurring.Interval.MONTH).build())\n                                            .build())\n                            .build());\n        }\n\n        Session session = Session.create(paramsBuilder.build());\n\n        return session.getUrl();\n    }\n<\/code><\/pre>\n<p>Dieser Code ist dem der Methode <code>newSubscription<\/code> sehr \u00e4hnlich. Der einzige (und wichtigste) Unterschied besteht darin, dass dem Objekt session create parameters ein Testzeitraum mit dem Wert <code>30<\/code> \u00fcbergeben wird, der einen kostenlosen Testzeitraum von 30 Tagen angibt.<\/p>\n<p>Du kannst die \u00c4nderungen nun speichern und den Entwicklungsserver f\u00fcr das Backend und das Frontend erneut starten, um den Workflow f\u00fcr das Abonnement mit kostenloser Testphase in Aktion zu sehen:<\/p>\n<figure id=\"attachment_163063\" aria-describedby=\"caption-attachment-163063\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163063 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/subscription-free-trial-flow-1024x522.gif\" alt=\"Ein User Flow, der zeigt, wie ein erfolgreicher Checkout f\u00fcr ein Abonnement mit zus\u00e4tzlicher kostenloser Testversion \u00fcber die von Stripe gehostete Seite aussieht.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163063\" class=\"wp-caption-text\">Ein Abonnement mit kostenloser Testphase<\/figcaption><\/figure>\n<h2>Rechnungen f\u00fcr Zahlungen generieren<\/h2>\n<p>Bei Abonnements erstellt Stripe automatisch Rechnungen f\u00fcr jede Zahlung, auch wenn es sich um eine Transaktion ohne Wert handelt, wie z.B. bei der Anmeldung zu einer Testversion. F\u00fcr einmalige Zahlungen kannst du bei Bedarf auch Rechnungen erstellen.<\/p>\n<p>Um alle Zahlungen mit Rechnungen zu verkn\u00fcpfen, aktualisiere den Body des Payloads, der in der <code>initiatePayment<\/code> Funktion der <code>CustomerDetails<\/code> Komponente in der Frontend-Anwendung gesendet wird, um die folgende Eigenschaft zu enthalten:<\/p>\n<pre><code class=\"language-js\">invoiceNeeded: true<\/code><\/pre>\n<p>Du musst diese Eigenschaft auch in den Body des Payloads einf\u00fcgen, der in der Funktion <code>createTransactionSecret<\/code> der Komponente <code>IntegratedCheckout<\/code> an den Server gesendet wird.<\/p>\n<p>Als N\u00e4chstes aktualisierst du die Backend-Routen, um nach dieser neuen Eigenschaft zu suchen und die Stripe SDK-Aufrufe entsprechend zu aktualisieren.<\/p>\n<p>F\u00fcr die gehostete Checkout-Methode aktualisierst du die Methode <code>hostedCheckout<\/code>, indem du die folgenden Codezeilen hinzuf\u00fcgst, um die Rechnungsstellungsfunktionalit\u00e4t hinzuzuf\u00fcgen:<\/p>\n<pre><code class=\"language-java\">@PostMapping(\"\/checkout\/hosted\")\n    String hostedCheckout(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        \/\/ \u2026 other operations being done after creating the SessionCreateParams builder instance       \n\n        \/\/ Add the following block of code just before the SessionCreateParams are built from the builder instance\n        if (requestDTO.isInvoiceNeeded()) {\n            paramsBuilder.setInvoiceCreation(SessionCreateParams.InvoiceCreation.builder().setEnabled(true).build());\n        }\n\n        Session session = Session.create(paramsBuilder.build());\n\n        return session.getUrl();\n    }<\/code><\/pre>\n<p>Dadurch wird nach dem Feld <code>invoiceNeeded<\/code> gesucht und die Erstellungsparameter werden entsprechend gesetzt.<\/p>\n<p>Das Hinzuf\u00fcgen einer Rechnung zu einer integrierten Zahlung ist etwas knifflig. Du kannst nicht einfach einen Parameter setzen, der Stripe anweist, automatisch eine Rechnung mit der Zahlung zu erstellen. Du musst die Rechnung manuell erstellen und dann eine verkn\u00fcpfte Zahlungsabsicht.<\/p>\n<p>Wenn die Zahlungsabsicht erfolgreich bezahlt und abgeschlossen wird, wird die Rechnung als bezahlt markiert; andernfalls bleibt die Rechnung unbezahlt. Das ist zwar logisch, aber die Umsetzung kann etwas kompliziert sein (vor allem, wenn es keine klaren Beispiele oder Verweise gibt, denen du folgen kannst).<\/p>\n<p>Um dies umzusetzen, musst du die Methode <code>integratedCheckout<\/code> so \u00e4ndern, dass sie wie folgt aussieht:<\/p>\n<pre><code class=\"language-java\">String integratedCheckout(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        Stripe.apiKey = STRIPE_API_KEY;\n\n        \/\/ Start by finding an existing customer or creating a new one if needed\n        Customer customer = CustomerUtil.findOrCreateCustomer(requestDTO.getCustomerEmail(), requestDTO.getCustomerName());\n\n        PaymentIntent paymentIntent;\n\n        if (!requestDTO.isInvoiceNeeded()) {\n            \/\/ If the invoice is not needed, create a PaymentIntent directly and send it to the client\n            PaymentIntentCreateParams params =\n                    PaymentIntentCreateParams.builder()\n                            .setAmount(Long.parseLong(calculateOrderAmount(requestDTO.getItems())))\n                            .setCurrency(\"usd\")\n                            .setCustomer(customer.getId())\n                            .setAutomaticPaymentMethods(\n                                    PaymentIntentCreateParams.AutomaticPaymentMethods\n                                            .builder()\n                                            .setEnabled(true)\n                                            .build()\n                            )\n                            .build();\n\n            paymentIntent = PaymentIntent.create(params);\n        } else {\n            \/\/ If invoice is needed, create the invoice object, add line items to it, and finalize it to create the PaymentIntent automatically\n            InvoiceCreateParams invoiceCreateParams = new InvoiceCreateParams.Builder()\n                    .setCustomer(customer.getId())\n                    .build();\n\n            Invoice invoice = Invoice.create(invoiceCreateParams);\n\n            \/\/ Add each item to the invoice one by one\n            for (Product product : requestDTO.getItems()) {\n\n                \/\/ Look for existing Product in Stripe before creating a new one\n                Product stripeProduct;\n\n                ProductSearchResult results = Product.search(ProductSearchParams.builder()\n                        .setQuery(\"metadata['app_id']:'\" + product.getId() + \"'\")\n                        .build());\n\n                if (results.getData().size() != 0)\n                    stripeProduct = results.getData().get(0);\n                else {\n\n                    \/\/ If a product is not found in Stripe database, create it\n                    ProductCreateParams productCreateParams = new ProductCreateParams.Builder()\n                            .setName(product.getName())\n                            .putMetadata(\"app_id\", product.getId())\n                            .build();\n\n                    stripeProduct = Product.create(productCreateParams);\n                }\n\n                \/\/ Create an invoice line item using the product object for the line item\n                InvoiceItemCreateParams invoiceItemCreateParams = new InvoiceItemCreateParams.Builder()\n                        .setInvoice(invoice.getId())\n                        .setQuantity(1L)\n                        .setCustomer(customer.getId())\n                        .setPriceData(\n                                InvoiceItemCreateParams.PriceData.builder()\n                                        .setProduct(stripeProduct.getId())\n                                        .setCurrency(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getCurrency())\n                                        .setUnitAmountDecimal(ProductDAO.getProduct(product.getId()).getDefaultPriceObject().getUnitAmountDecimal())\n                                        .build())\n                        .build();\n\n                InvoiceItem.create(invoiceItemCreateParams);\n            }\n\n            \/\/ Mark the invoice as final so that a PaymentIntent is created for it\n            invoice = invoice.finalizeInvoice();\n\n            \/\/ Retrieve the payment intent object from the invoice\n            paymentIntent = PaymentIntent.retrieve(invoice.getPaymentIntent());\n        }\n\n        \/\/ Send the client secret from the payment intent to the client\n        return paymentIntent.getClientSecret();\n    }\n<\/code><\/pre>\n<p>Der alte Code dieser Methode wird in den Block <code>if<\/code> verschoben, der pr\u00fcft, ob das Feld <code>invoiceNeeded<\/code> <code>false<\/code> ist. Wenn dies der Fall ist, erstellt die Methode jetzt eine Rechnung mit Rechnungsposten und markiert sie als abgeschlossen, damit sie bezahlt werden kann.<\/p>\n<p>Dann ruft sie die Zahlungsabsicht ab, die automatisch erstellt wurde, als die Rechnung abgeschlossen wurde, und sendet das Kundengeheimnis aus dieser Zahlungsabsicht an den Kunden. Sobald der Kunde die integrierte Kaufabwicklung abgeschlossen hat, wird die Zahlung eingezogen und die Rechnung als bezahlt markiert.<\/p>\n<p>Damit ist die Einrichtung abgeschlossen und du kannst mit der Erstellung von Rechnungen \u00fcber deine Anwendung beginnen. In deinem Stripe-Dashboard kannst du im <a href=\"https:\/\/dashboard.example.com\/test\/invoices\" target=\"_blank\" rel=\"noopener noreferrer\">Bereich Rechnungen<\/a> die Rechnungen einsehen, die deine Anwendung bei jedem Kauf und jeder Abo-Zahlung erstellt.<\/p>\n<p>Stripe bietet dir aber auch die M\u00f6glichkeit, \u00fcber seine API auf die Rechnungen zuzugreifen, damit deine Kunden die Rechnungen jederzeit selbst herunterladen k\u00f6nnen.<\/p>\n<p>Dazu erstellst du eine neue Komponente im Verzeichnis <strong>frontend\/routes<\/strong> mit dem Namen <strong>ViewInvoices.tsx<\/strong>. F\u00fcge den folgenden Code darin ein:<\/p>\n<pre><code class=\"language-js\">import {Button, Card, Center, Heading, HStack, IconButton, Input, Text, VStack} from \"@chakra-ui\/react\";\nimport {useState} from \"react\";\nimport {DownloadIcon} from \"@chakra-ui\/icons\";\n\nfunction ViewInvoices() {\n    const [email, setEmail] = useState(\"\")\n    const [invoices, setInvoices] = useState&lt;InvoiceData[]&gt;([])\n\n    const onCustomerEmailChange = (ev: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {\n        setEmail(ev.target.value)\n    }\n\n    const listInvoices = () =&gt; {\n\n        fetch(process.env.VITE_SERVER_BASE_URL + \"\/invoices\/list\", {\n            method: \"POST\",\n            headers: {'Content-Type': 'application\/json'},\n            body: JSON.stringify({\n                customerEmail: email,\n            })\n        })\n            .then(r =&gt; r.json())\n            .then((r: InvoiceData[]) =&gt; {\n                setInvoices(r)\n            })\n\n    }\n\n    return &lt;&gt;\n        &lt;Center h={'100vh'} color='black'&gt;\n            &lt;VStack spacing={3} width={'xl'}&gt;\n                &lt;Heading&gt;View Invoices for Customer&lt;\/Heading&gt;\n                {(invoices.length === 0 ? &lt;&gt;\n                    &lt;Input variant='filled' placeholder='Customer Email' onChange={onCustomerEmailChange}\n                           value={email}\/&gt;\n                    &lt;Button onClick={listInvoices} colorScheme={'green'}&gt;Look up Invoices&lt;\/Button&gt;\n                &lt;\/&gt; : &lt;&gt;&lt;\/&gt;)}\n                {invoices.map(elem =&gt; {\n                    return &lt;Card direction={{base: 'column', sm: 'row'}}\n                                 overflow='hidden'\n                                 alignItems={'center'}\n                                 justifyContent={'space-between'}\n                                 padding={'8px'}\n                                 width={500}\n                                 variant='outline'&gt;\n                        &lt;Text&gt;\n                            {elem.number}\n                        &lt;\/Text&gt;\n                        &lt;HStack spacing={\"3\"}&gt;\n                            &lt;Text color='blue.600' fontSize='2xl'&gt;\n                                {\"$\" + elem.amount}\n                            &lt;\/Text&gt;\n                            &lt;IconButton onClick={() =&gt; {\n                                window.location.href = elem.url\n                            }} icon={&lt;DownloadIcon\/&gt;} aria-label={'Download invoice'}\/&gt;\n                        &lt;\/HStack&gt;\n                    &lt;\/Card&gt;\n                })}\n            &lt;\/VStack&gt;\n        &lt;\/Center&gt;\n    &lt;\/&gt;\n}\n\ninterface InvoiceData {\n    number: string,\n    amount: string,\n    url: string\n}\n\nexport default ViewInvoices\n<\/code><\/pre>\n<p>\u00c4hnlich wie bei der Komponente <code>CancelSubscription<\/code> zeigt diese Komponente ein Eingabefeld an, in das der Kunde seine E-Mail eingeben kann, sowie eine Schaltfl\u00e4che, mit der er nach Rechnungen suchen kann. Sobald Rechnungen gefunden wurden, werden das Eingabefeld und die Schaltfl\u00e4che ausgeblendet und dem Kunden wird eine Liste der Rechnungen mit der Rechnungsnummer, dem Gesamtbetrag und einer Schaltfl\u00e4che zum Herunterladen des Rechnungs-PDFs angezeigt.<\/p>\n<p>Um die Backend-Methode zu implementieren, die nach Rechnungen eines bestimmten Kunden sucht und die entsprechenden Informationen (Rechnungsnummer, Betrag und PDF-URL) zur\u00fcckschickt, f\u00fcgst du die folgende Methode in deine <code>PaymentController<\/code> Klasse im Backend ein;<\/p>\n<pre><code class=\"language-java\">@PostMapping(\"\/invoices\/list\")\n    List&lt;Map&lt;String, String&gt;&gt; listInvoices(@RequestBody RequestDTO requestDTO) throws StripeException {\n\n        Stripe.apiKey = STRIPE_API_KEY;\n\n        \/\/ Start by finding existing customer record from Stripe\n        Customer customer = CustomerUtil.findCustomerByEmail(requestDTO.getCustomerEmail());\n\n        \/\/ If no customer record was found, no subscriptions exist either, so return an empty list\n        if (customer == null) {\n            return new ArrayList&lt;&gt;();\n        }\n\n        \/\/ Search for invoices for the current customer\n        Map&lt;String, Object&gt; invoiceSearchParams = new HashMap&lt;&gt;();\n        invoiceSearchParams.put(\"customer\", customer.getId());\n        InvoiceCollection invoices =\n                Invoice.list(invoiceSearchParams);\n\n        List&lt;Map&lt;String, String&gt;&gt; response = new ArrayList&lt;&gt;();\n\n        \/\/ For each invoice, extract its number, amount, and PDF URL to send to the client\n        for (Invoice invoice : invoices.getData()) {\n            HashMap&lt;String, String&gt; map = new HashMap&lt;&gt;();\n\n            map.put(\"number\", invoice.getNumber());\n            map.put(\"amount\", String.valueOf((invoice.getTotal() \/ 100f)));\n            map.put(\"url\", invoice.getInvoicePdf());\n\n            response.add(map);\n        }\n\n        return response;\n    }\n<\/code><\/pre>\n<p>Die Methode sucht den Kunden zun\u00e4chst \u00fcber die angegebene E-Mail-Adresse. Dann sucht sie nach Rechnungen dieses Kunden, die als bezahlt markiert sind. Sobald die Liste der Rechnungen gefunden ist, extrahiert sie die Rechnungsnummer, den Betrag und die PDF-URL und sendet eine Liste mit diesen Informationen an die Kunden-Anwendung zur\u00fcck.<\/p>\n<p>So sieht der Fluss der Rechnungen aus:<\/p>\n<figure id=\"attachment_163064\" aria-describedby=\"caption-attachment-163064\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163064 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/viewing-invoices-flow-1024x522.gif\" alt=\"Ein User Flow, der zeigt, wie man Rechnungen f\u00fcr einen Benutzer abruft und darauf zugreift.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163064\" class=\"wp-caption-text\">Anzeigen von Rechnungen<\/figcaption><\/figure>\n<p>Damit ist die Entwicklung unserer Java-Demo-Anwendung (<a href=\"https:\/\/github.com\/krharsh17\/stripe-payments-java-react-frontend\" target=\"_blank\" rel=\"noopener noreferrer\">Frontend<\/a> &#038; <a href=\"https:\/\/github.com\/krharsh17\/stripe-payments-java-react-backend\" target=\"_blank\" rel=\"noopener noreferrer\">Backend<\/a>) abgeschlossen. Im n\u00e4chsten Abschnitt erf\u00e4hrst du, wie du diese Anwendung auf Kinsta bereitstellst, damit du sie online nutzen kannst.<\/p>\n<h2>Bereitstellen deiner Anwendung auf Kinsta<\/h2>\n<p>Sobald deine Anwendung fertig ist, kannst du sie auf Kinsta bereitstellen. Kinsta unterst\u00fctzt Bereitstellungen von deinem bevorzugten Git-Anbieter (<a href=\"https:\/\/docs.sevalla.com\/applications\/git\/bitbucket#grant-access-to-the-kinsta-bitbucket-application\">Bitbucket<\/a>, <a href=\"https:\/\/docs.sevalla.com\/applications\/git\/github#authenticate-and-authorize\">GitHub<\/a> oder <a href=\"https:\/\/docs.sevalla.com\/applications\/git\/gitlab#authorize-the-kinsta-gitlab-application\">GitLab<\/a>). Verbinde die Quellcode-Repositories deiner Anwendung mit Kinsta und Kinsta stellt deine Anwendung automatisch bereit, sobald es eine \u00c4nderung am Code gibt.<\/p>\n<h3>Bereite deine Projekte vor<\/h3>\n<p>Um deine Anwendungen in die Produktion zu \u00fcberf\u00fchren, musst du die Build- und Deploy-Befehle festlegen, die Kinsta verwenden soll. F\u00fcr das Frontend musst du sicherstellen, dass in deiner <strong>package.json-Datei<\/strong> die folgenden Skripte definiert sind:<\/p>\n<pre><code class=\"language-bash\">\"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"NODE_ENV=production tsc && vite build\",\n    \"start\": \"serve .\/dist\",\n    \"lint\": \"eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"preview\": \"vite preview\"\n  },\n<\/code><\/pre>\n<p>Au\u00dferdem musst du das Paket <a href=\"https:\/\/www.npmjs.com\/package\/serve\" target=\"_blank\" rel=\"noopener noreferrer\">serve<\/a> npm installieren, mit dem du statische Websites bereitstellen kannst. Dieses Paket wird verwendet, um den Produktions-Build deiner Anwendung aus der Kinsta-Bereitstellungsumgebung bereitzustellen. Du kannst es installieren, indem du den folgenden Befehl ausf\u00fchrst:<\/p>\n<pre><code class=\"language-bash\">npm i serve<\/code><\/pre>\n<p>Wenn du deine Anwendung mit vite erstellst, wird die gesamte Anwendung in eine einzige Datei, <strong>index.html<\/strong>, gepackt, da die Konfiguration von React, die du in diesem Tutorial verwendest, f\u00fcr die Erstellung von Single Page Applications gedacht ist. F\u00fcr deine Nutzer\/innen macht das zwar keinen gro\u00dfen Unterschied, aber du musst einige zus\u00e4tzliche Konfigurationen einrichten, um das Browser-Routing und die Navigation in solchen Anwendungen zu steuern.<\/p>\n<p>Mit der aktuellen Konfiguration kannst du deine App nur \u00fcber die Basis-URL deines Deployments aufrufen. Wenn die Basis-URL des Einsatzes <strong>example.com<\/strong> ist, f\u00fchren alle Anfragen an <strong>example.com\/some-route<\/strong> zu HTTP 404 Fehlern.<\/p>\n<p>Das liegt daran, dass dein Server nur eine Datei, die <strong>index.html-Datei<\/strong>, bereitstellen kann. Eine Anfrage, die an <strong>example.com\/some-route<\/strong> gesendet wird, sucht nach der Datei <strong>some-route\/index.html<\/strong>, die nicht existiert und erh\u00e4lt daher die Antwort 404 Not Found.<\/p>\n<p>Um dies zu beheben, erstelle eine Datei namens <strong>serve.json<\/strong> in deinem Ordner <strong>frontend\/public<\/strong> und speichere den folgenden Code darin:<\/p>\n<pre><code class=\"language-js\">{\n  \"rewrites\": [\n    { \"source\": \"*\", \"destination\": \"\/index.html\" }\n  ]\n}\n<\/code><\/pre>\n<p>Diese Datei weist <code>serve<\/code> an, alle eingehenden Anfragen so umzuschreiben, dass sie an die Datei <strong>index.html<\/strong> weitergeleitet werden, w\u00e4hrend in der Antwort weiterhin der Pfad angezeigt wird, an den die urspr\u00fcngliche Anfrage gesendet wurde. So kannst du die Erfolgs- und Misserfolgsseiten deiner Anwendung korrekt anzeigen, wenn Stripe deine Kunden zu deiner Anwendung zur\u00fcckleitet.<\/p>\n<p>F\u00fcr das Backend erstellst du ein <a href=\"https:\/\/kinqsta.com\/de\/blog\/dockerfile-entrypoint\/\">Dockerfile<\/a>, um genau die richtige Umgebung f\u00fcr deine Java-Anwendung einzurichten. Mit einem Dockerfile stellst du sicher, dass die Umgebung f\u00fcr deine Java-Anwendung auf allen Hosts gleich ist (sei es dein lokaler Entwicklungshost oder der Kinsta Bereitstellungs-Host) und du kannst sicherstellen, dass deine Anwendung wie erwartet l\u00e4uft.<\/p>\n<p>Dazu erstellst du eine Datei namens <strong>Dockerfile<\/strong> im <strong>Backend-Ordner<\/strong> und speicherst den folgenden Inhalt darin:<\/p>\n<pre><code class=\"language-bash\">FROM openjdk:22-oraclelinux8\n\nLABEL maintainer=\"krharsh17\"\n\nWORKDIR \/app\n\nCOPY . \/app\n\nRUN .\/mvnw clean package\n\nEXPOSE 8080\n\nENTRYPOINT [\"java\", \"-jar\", \"\/app\/target\/backend.jar\"]<\/code><\/pre>\n<p>Diese Datei weist die Runtime an, das <a href=\"https:\/\/openjdk.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenJDK-Java-Image<\/a> als Basis f\u00fcr den Deployment-Container zu verwenden, den Befehl <code>.\/mvnw clean package<\/code> auszuf\u00fchren, um die <strong>JAR-Datei<\/strong> deiner Anwendung zu bauen, und den Befehl <code>java -jar &lt;jar-file&gt;<\/code> zu verwenden, um sie auszuf\u00fchren. Damit ist die Vorbereitung des Quellcodes f\u00fcr die Bereitstellung auf Kinsta abgeschlossen.<\/p>\n<h3>GitHub-Repositories einrichten<\/h3>\n<p>Um mit der Bereitstellung der Anwendungen zu beginnen, musst du zwei GitHub-Repositories einrichten, in denen der Quellcode deiner Anwendungen gespeichert wird. Wenn du das GitHub CLI verwendest, kannst du dies \u00fcber das Terminal tun, indem du die folgenden Befehle ausf\u00fchrst:<\/p>\n<pre><code class=\"language-bash\"># Run these in the backend folder\ngh repo create stripe-payments-java-react-backend --public --source=. --remote=origin\ngit init\ngit add .\ngit commit -m \"Initial commit\"\ngit push origin main\n\n# Run these in the frontend folder\ngh repo create stripe-payments-java-react-frontend --public --source=. --remote=origin\ngit init\ngit add .\ngit commit -m \"Initial commit\"\ngit push origin main<\/code><\/pre>\n<p>Damit solltest du neue GitHub-Repositories in deinem Konto erstellen und den Code deiner Anwendungen dorthin pushen. Du solltest nun auf die Frontend- und Backend-Repositories zugreifen k\u00f6nnen. Als N\u00e4chstes stellst du diese Repositories auf Kinsta bereit, indem du die folgenden Schritte ausf\u00fchrst:<\/p>\n<ol>\n<li>Melde dich im <a href=\"https:\/\/my.kinqsta.com\/?lang=de\">MyKinsta-Dashboard<\/a> an oder erstelle dein Kinsta-Konto.<\/li>\n<li>Klicke in der linken Seitenleiste auf <strong>Anwendungen<\/strong> und dann auf <strong>Anwendung hinzuf\u00fcgen<\/strong>.<\/li>\n<li>W\u00e4hle in dem daraufhin angezeigten Fenster das Repository aus, das du bereitstellen m\u00f6chtest. Wenn du mehrere Zweige hast, kannst du den gew\u00fcnschten Zweig ausw\u00e4hlen und einen Namen f\u00fcr deine Anwendung vergeben.<\/li>\n<li>W\u00e4hle einen der verf\u00fcgbaren Rechenzentrumsstandorte aus der Liste der 24 Optionen. Kinsta erkennt automatisch den Startbefehl f\u00fcr deine Anwendung.<\/li>\n<\/ol>\n<p>Denke daran, dass du sowohl deine Frontend- als auch deine Backend-Anwendung mit einigen <a href=\"https:\/\/kinqsta.com\/de\/blog\/was-ist-eine-umgebungsvariable\/\">Umgebungsvariablen<\/a> ausstatten musst, damit sie richtig funktionieren. Die Frontend-Anwendung ben\u00f6tigt die folgenden Umgebungsvariablen:<\/p>\n<ul>\n<li>VITE_STRIPE_API_KEY<\/li>\n<li>VITE_SERVER_BASE_URL<\/li>\n<li>VITE_CLIENT_BASE_URL<\/li>\n<\/ul>\n<p>F\u00fcr die Bereitstellung der Backend-Anwendung tust du genau das, was wir f\u00fcr das Frontend getan haben, aber f\u00fcr den Schritt <strong>Build environment<\/strong> w\u00e4hlst du das Optionsfeld <strong>Use Dockerfile to set up container image<\/strong>\u00a0aus und gibst <code>Dockerfile<\/code> als Pfad f\u00fcr die Dockerdatei deiner Backend-Anwendung ein.<\/p>\n<figure id=\"attachment_163065\" aria-describedby=\"caption-attachment-163065\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163065 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/application-form-build-environment-details-1024x523.png\" alt=\"Im Antragsformular f\u00fcr das Hinzuf\u00fcgen wirst du aufgefordert, Angaben zur Build-Umgebung zu machen.\" width=\"1024\" height=\"523\"><figcaption id=\"caption-attachment-163065\" class=\"wp-caption-text\">Einstellen der Details der Build-Umgebung<\/figcaption><\/figure>\n<p>Vergiss nicht, die Backend-Umgebungsvariablen hinzuzuf\u00fcgen:<\/p>\n<ul>\n<li>CLIENT_BASE_URL<\/li>\n<li>STRIPE_API_KEY<\/li>\n<\/ul>\n<p>Sobald die Bereitstellung abgeschlossen ist, rufe die Detailseite deiner Anwendung auf und rufe dort die URL der Bereitstellung auf.<\/p>\n<figure id=\"attachment_163066\" aria-describedby=\"caption-attachment-163066\" style=\"width: 1024px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-163066 size-large\" src=\"https:\/\/kinqsta.com\/wp-content\/uploads\/2023\/09\/hosted-url-for-kinsta-deployed-apps-1024x523.png\" alt=\"Die Anwendungs-Detailseite mit einem roten Kasten, der anzeigt, wo du die URL des Einsatzes findest.\" width=\"1024\" height=\"523\"><figcaption id=\"caption-attachment-163066\" class=\"wp-caption-text\">Die gehostete URL f\u00fcr die auf Kinsta bereitgestellten Anwendungen<\/figcaption><\/figure>\n<p>Extrahiere die URLs f\u00fcr die beiden bereitgestellten Anwendungen. Gehe zum <a href=\"https:\/\/dashboard.example.com\/test\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe-Dashboard<\/a>, um deine geheimen und ver\u00f6ffentlichbaren API-Schl\u00fcssel zu erhalten.<\/p>\n<p>Achte darauf, dass du deiner Frontend-Anwendung den ver\u00f6ffentlichbaren Stripe-Schl\u00fcssel zur Verf\u00fcgung stellst (nicht den geheimen Schl\u00fcssel). Achte au\u00dferdem darauf, dass deine Basis-URLs keinen Schr\u00e4gstrich (<code>\/<\/code>) am Ende haben. Die Routen haben bereits f\u00fchrende Schr\u00e4gstriche, so dass ein nachfolgender Schr\u00e4gstrich am Ende der Basis-URLs dazu f\u00fchrt, dass zwei Schr\u00e4gstriche zu den endg\u00fcltigen URLs hinzugef\u00fcgt werden.<\/p>\n<p>F\u00fcr deine Backend-Anwendung f\u00fcgst du den geheimen Schl\u00fcssel aus dem Stripe-Dashboard hinzu (nicht den ver\u00f6ffentlichbaren Schl\u00fcssel). Achte au\u00dferdem darauf, dass deine Client-URL keinen Schr\u00e4gstrich (<code>\/<\/code>) am Ende hat.<\/p>\n<p>Sobald die Variablen hinzugef\u00fcgt sind, gehst du auf die Registerkarte <strong>Anwendungsbereitstellungen<\/strong> und klickst auf die Schaltfl\u00e4che <strong>Neu bereitstellen<\/strong> f\u00fcr deine Backend-Anwendung. Damit ist die einmalige Einrichtung abgeschlossen, die du ben\u00f6tigst, um deine Kinsta-Eins\u00e4tze \u00fcber Umgebungsvariablen mit Anmeldeinformationen zu versorgen.<\/p>\n<p>Nun kannst du die \u00c4nderungen in deine Versionskontrolle \u00fcbertragen. Kinsta wird deine Anwendung automatisch neu bereitstellen, wenn du die Option beim Deployment aktiviert hast; andernfalls musst du die Neubereitstellung manuell ausl\u00f6sen.<\/p>\n<h2>Zusammenfassung<\/h2>\n<p>In diesem Artikel hast du gelernt, wie Stripe funktioniert und welche Zahlungsstr\u00f6me es bietet. Au\u00dferdem hast du anhand eines detaillierten Beispiels gelernt, wie du Stripe in deine Java-Anwendung integrierst, um Einmalzahlungen zu akzeptieren, Abonnements einzurichten, kostenlose Testversionen anzubieten und Rechnungen zu erstellen.<\/p>\n<p>Wenn du Stripe und Java zusammen verwendest, kannst du deinen Kunden eine robuste Zahlungsl\u00f6sung anbieten, die gut skalierbar ist und sich nahtlos in dein bestehendes \u00d6kosystem von Anwendungen und Tools integrieren l\u00e4sst.<\/p>\n<p><em>Nutzt du Stripe in deiner Anwendung, um Zahlungen einzuziehen? Wenn ja, welche der beiden M\u00f6glichkeiten bevorzugst du: gehostet, benutzerdefiniert oder in der Anwendung? Lass es uns in den Kommentaren unten wissen!<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mit der Zunahme digitaler Transaktionen ist die F\u00e4higkeit, Zahlungsgateways nahtlos zu integrieren, zu einer entscheidenden F\u00e4higkeit f\u00fcr Entwickler geworden. Ob f\u00fcr Marktpl\u00e4tze oder SaaS-Produkte, ein Zahlungsabwickler &#8230;<\/p>\n","protected":false},"author":199,"featured_media":65946,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kinsta_gated_content":false,"_kinsta_gated_content_redirect":"","footnotes":""},"tags":[],"topic":[925,975,990],"class_list":["post-65945","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","topic-api","topic-react","topic-webentwicklung-sprachen"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v24.6 (Yoast SEO v24.6) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Ein Leitfaden zur Integration von Stripe in Spring Boot-Anwendungen - Kinsta\u00ae<\/title>\n<meta name=\"description\" content=\"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Ein Leitfaden zur Stripe-Integration in Spring Boot\" \/>\n<meta property=\"og:description\" content=\"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/\" \/>\n<meta property=\"og:site_name\" content=\"Kinsta\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/\" \/>\n<meta property=\"article:published_time\" content=\"2023-10-02T08:24:16+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-10-24T09:16:10+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1460\" \/>\n\t<meta property=\"og:image:height\" content=\"730\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Jeremy Holcombe\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:description\" content=\"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@Kinsta_DE\" \/>\n<meta name=\"twitter:site\" content=\"@Kinsta_DE\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jeremy Holcombe\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"47\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/\"},\"author\":{\"name\":\"Jeremy Holcombe\",\"@id\":\"https:\/\/kinqsta.com\/de\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21\"},\"headline\":\"Ein Leitfaden zur Stripe-Integration in Spring Boot\",\"datePublished\":\"2023-10-02T08:24:16+00:00\",\"dateModified\":\"2023-10-24T09:16:10+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/\"},\"wordCount\":7176,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/kinqsta.com\/de\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg\",\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/\",\"url\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/\",\"name\":\"Ein Leitfaden zur Integration von Stripe in Spring Boot-Anwendungen - Kinsta\u00ae\",\"isPartOf\":{\"@id\":\"https:\/\/kinqsta.com\/de\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg\",\"datePublished\":\"2023-10-02T08:24:16+00:00\",\"dateModified\":\"2023-10-24T09:16:10+00:00\",\"description\":\"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage\",\"url\":\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg\",\"contentUrl\":\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg\",\"width\":1460,\"height\":730},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/kinqsta.com\/de\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"API\",\"item\":\"https:\/\/kinqsta.com\/de\/thema\/api\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Ein Leitfaden zur Stripe-Integration in Spring Boot\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/kinqsta.com\/de\/#website\",\"url\":\"https:\/\/kinqsta.com\/de\/\",\"name\":\"Kinsta\u00ae\",\"description\":\"Schnelle, sichere und hochwertige Hosting-L\u00f6sungen\",\"publisher\":{\"@id\":\"https:\/\/kinqsta.com\/de\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/kinqsta.com\/de\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/kinqsta.com\/de\/#organization\",\"name\":\"Kinsta\",\"url\":\"https:\/\/kinqsta.com\/de\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/kinqsta.com\/de\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg\",\"contentUrl\":\"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg\",\"width\":500,\"height\":500,\"caption\":\"Kinsta\"},\"image\":{\"@id\":\"https:\/\/kinqsta.com\/de\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/\",\"https:\/\/x.com\/Kinsta_DE\",\"https:\/\/www.instagram.com\/kinstahosting\/\",\"https:\/\/www.linkedin.com\/company\/kinsta\/\",\"https:\/\/www.pinterest.com\/kinstahosting\/\",\"https:\/\/www.youtube.com\/c\/Kinsta\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/kinqsta.com\/de\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21\",\"name\":\"Jeremy Holcombe\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/kinqsta.com\/de\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/0e17001f3bb37dbbe54fceef9bb547fa?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/0e17001f3bb37dbbe54fceef9bb547fa?s=96&d=mm&r=g\",\"caption\":\"Jeremy Holcombe\"},\"description\":\"Senior Editor at Kinsta, WordPress Web Developer, and Content Writer. Outside of all things WordPress, I enjoy the beach, golf, and movies. I also have tall people problems.\",\"sameAs\":[\"https:\/\/www.linkedin.com\/in\/jeremyholcombe\/\"],\"url\":\"https:\/\/kinqsta.com\/de\/blog\/author\/jeremyholcombe\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Ein Leitfaden zur Integration von Stripe in Spring Boot-Anwendungen - Kinsta\u00ae","description":"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/","og_locale":"de_DE","og_type":"article","og_title":"Ein Leitfaden zur Stripe-Integration in Spring Boot","og_description":"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.","og_url":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/","article_published_time":"2023-10-02T08:24:16+00:00","article_modified_time":"2023-10-24T09:16:10+00:00","og_image":[{"width":1460,"height":730,"url":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg","type":"image\/jpeg"}],"author":"Jeremy Holcombe","twitter_card":"summary_large_image","twitter_description":"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.","twitter_image":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg","twitter_creator":"@Kinsta_DE","twitter_site":"@Kinsta_DE","twitter_misc":{"Verfasst von":"Jeremy Holcombe","Gesch\u00e4tzte Lesezeit":"47\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#article","isPartOf":{"@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/"},"author":{"name":"Jeremy Holcombe","@id":"https:\/\/kinqsta.com\/de\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21"},"headline":"Ein Leitfaden zur Stripe-Integration in Spring Boot","datePublished":"2023-10-02T08:24:16+00:00","dateModified":"2023-10-24T09:16:10+00:00","mainEntityOfPage":{"@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/"},"wordCount":7176,"commentCount":0,"publisher":{"@id":"https:\/\/kinqsta.com\/de\/#organization"},"image":{"@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage"},"thumbnailUrl":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg","inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/","url":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/","name":"Ein Leitfaden zur Integration von Stripe in Spring Boot-Anwendungen - Kinsta\u00ae","isPartOf":{"@id":"https:\/\/kinqsta.com\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage"},"image":{"@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage"},"thumbnailUrl":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg","datePublished":"2023-10-02T08:24:16+00:00","dateModified":"2023-10-24T09:16:10+00:00","description":"Lerne, wie du Stripe nahtlos in deine Spring Boot-Anwendung einbinden kannst, um eine effiziente und benutzerfreundliche Zahlungsabwicklung zu erm\u00f6glichen.","breadcrumb":{"@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#primaryimage","url":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg","contentUrl":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/09\/stripe-java-api.jpg","width":1460,"height":730},{"@type":"BreadcrumbList","@id":"https:\/\/kinqsta.com\/de\/blog\/stripe-java-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/kinqsta.com\/de\/"},{"@type":"ListItem","position":2,"name":"API","item":"https:\/\/kinqsta.com\/de\/thema\/api\/"},{"@type":"ListItem","position":3,"name":"Ein Leitfaden zur Stripe-Integration in Spring Boot"}]},{"@type":"WebSite","@id":"https:\/\/kinqsta.com\/de\/#website","url":"https:\/\/kinqsta.com\/de\/","name":"Kinsta\u00ae","description":"Schnelle, sichere und hochwertige Hosting-L\u00f6sungen","publisher":{"@id":"https:\/\/kinqsta.com\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/kinqsta.com\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/kinqsta.com\/de\/#organization","name":"Kinsta","url":"https:\/\/kinqsta.com\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/kinqsta.com\/de\/#\/schema\/logo\/image\/","url":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg","contentUrl":"https:\/\/kinqsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg","width":500,"height":500,"caption":"Kinsta"},"image":{"@id":"https:\/\/kinqsta.com\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/","https:\/\/x.com\/Kinsta_DE","https:\/\/www.instagram.com\/kinstahosting\/","https:\/\/www.linkedin.com\/company\/kinsta\/","https:\/\/www.pinterest.com\/kinstahosting\/","https:\/\/www.youtube.com\/c\/Kinsta"]},{"@type":"Person","@id":"https:\/\/kinqsta.com\/de\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21","name":"Jeremy Holcombe","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/kinqsta.com\/de\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/0e17001f3bb37dbbe54fceef9bb547fa?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/0e17001f3bb37dbbe54fceef9bb547fa?s=96&d=mm&r=g","caption":"Jeremy Holcombe"},"description":"Senior Editor at Kinsta, WordPress Web Developer, and Content Writer. Outside of all things WordPress, I enjoy the beach, golf, and movies. I also have tall people problems.","sameAs":["https:\/\/www.linkedin.com\/in\/jeremyholcombe\/"],"url":"https:\/\/kinqsta.com\/de\/blog\/author\/jeremyholcombe\/"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/posts\/65945","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/users\/199"}],"replies":[{"embeddable":true,"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/comments?post=65945"}],"version-history":[{"count":7,"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/posts\/65945\/revisions"}],"predecessor-version":[{"id":65980,"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/posts\/65945\/revisions\/65980"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinqsta.com\/de\/wp-json\/kinsta\/v1\/posts\/65945\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinqsta.com\/de\/wp-json\/kinsta\/v1\/posts\/65945\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinqsta.com\/de\/wp-json\/kinsta\/v1\/posts\/65945\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinqsta.com\/de\/wp-json\/kinsta\/v1\/posts\/65945\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinqsta.com\/de\/wp-json\/kinsta\/v1\/posts\/65945\/translations\/de"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinqsta.com\/de\/wp-json\/kinsta\/v1\/posts\/65945\/translations\/es"},{"href":"https:\/\/kinqsta.com\/de\/wp-json\/kinsta\/v1\/posts\/65945\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/media\/65946"}],"wp:attachment":[{"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/media?parent=65945"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/tags?post=65945"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinqsta.com\/de\/wp-json\/wp\/v2\/topic?post=65945"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}