{"id":70437,"date":"2023-10-02T16:31:49","date_gmt":"2023-10-02T14:31:49","guid":{"rendered":"https:\/\/kinsta.com\/es\/?p=70437&#038;preview=true&#038;preview_id=70437"},"modified":"2023-10-12T17:59:28","modified_gmt":"2023-10-12T15:59:28","slug":"stripe-java-api","status":"publish","type":"post","link":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/","title":{"rendered":"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot"},"content":{"rendered":"<p>A medida que aumentan las transacciones digitales, la capacidad de integrar perfectamente las pasarelas de pago se ha convertido en una habilidad cr\u00edtica para los desarrolladores. Ya sea para marketplaces o <a href=\"https:\/\/kinsta.com\/es\/blog\/productos-saas\/\">productos SaaS<\/a>, un procesador de pagos es crucial para cobrar y procesar los pagos de los usuarios.<\/p>\n<p>Este art\u00edculo explica c\u00f3mo integrar <a href=\"https:\/\/kinsta.com\/es\/blog\/stripe-vs-adyen\/\">Stripe<\/a> en un entorno <a href=\"https:\/\/spring.io\/projects\/spring-boot\" target=\"_blank\" rel=\"noopener noreferrer\">Spring Boot<\/a>, c\u00f3mo configurar suscripciones, ofrecer pruebas gratuitas y crear p\u00e1ginas de autoservicio para que tus clientes descarguen sus facturas.<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>\u00bfQu\u00e9 es Stripe?<\/h2>\n<p><a href=\"https:\/\/stripe.com\/en-in\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe<\/a> es una plataforma de procesamiento de pagos de renombre mundial disponible en <a href=\"https:\/\/stripe.com\/en-in\/global\" target=\"_blank\" rel=\"noopener noreferrer\">46 pa\u00edses<\/a>. Es una gran elecci\u00f3n si quieres crear una integraci\u00f3n de pagos en tu aplicaci\u00f3n web gracias a su gran alcance, su reputaci\u00f3n y su detallada documentaci\u00f3n.<\/p>\n<h3>Comprender los conceptos comunes de Stripe<\/h3>\n<p>Es \u00fatil entender algunos conceptos comunes que Stripe utiliza para coordinar y llevar a cabo operaciones de pago entre m\u00faltiples partes. Stripe ofrece dos enfoques para implementar la integraci\u00f3n de pagos en tu aplicaci\u00f3n.<\/p>\n<p>Puedes incrustar los formularios de Stripe dentro de tu aplicaci\u00f3n para una experiencia de cliente dentro de la aplicaci\u00f3n (<a href=\"https:\/\/stripe.com\/docs\/payments\/payment-intents\" target=\"_blank\" rel=\"noopener noreferrer\">Payment Intent<\/a>) o redirigir a los clientes a una p\u00e1gina de pago alojada en Stripe, donde Stripe gestiona el proceso e informa a tu aplicaci\u00f3n cuando un pago se realiza correctamente o no (<a href=\"https:\/\/stripe.com\/docs\/payment-links\" target=\"_blank\" rel=\"noopener noreferrer\">Payment Link<\/a>).<\/p>\n<h4 id=\"payment-intent\" class=\"has-anchor-hash\">Payment Intent (Intenci\u00f3n de pago)<\/h4>\n<p>Al gestionar los pagos, es importante recopilar por adelantado los detalles del cliente y del producto antes de solicitarles la informaci\u00f3n de la tarjeta y el pago. Estos datos incluyen la descripci\u00f3n, el importe total, el modo de pago, etc.<\/p>\n<p>Stripe requiere que recopiles estos datos en tu aplicaci\u00f3n y generes un objeto <code>PaymentIntent<\/code> en su backend. Este enfoque permite a Stripe formular una solicitud de pago para esa intenci\u00f3n. Una vez concluido el pago, puedes recuperar sistem\u00e1ticamente los detalles del pago, incluido su prop\u00f3sito, a trav\u00e9s del objeto <code>PaymentIntent<\/code>.<\/p>\n<h4>Payment Link (Enlace de pago)<\/h4>\n<p>Para evitar las complejidades de integrar Stripe directamente en tu c\u00f3digo base, considera utilizar <a href=\"https:\/\/stripe.com\/docs\/payments\/checkout\/how-checkout-works\" target=\"_blank\" rel=\"noopener noreferrer\">Stripe Checkouts<\/a> para una soluci\u00f3n de pago alojada. Al igual que al crear un <code>PaymentIntent<\/code>, crear\u00e1s un <code>CheckoutSession<\/code> con los detalles del pago y del cliente. En lugar de iniciar un pago en la aplicaci\u00f3n <code>PaymentIntent<\/code>, el <code>CheckoutSession<\/code> genera un enlace de pago al que rediriges a tus clientes. Este es el aspecto de una p\u00e1gina de pago alojada:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/stripe-hosted-checkout-page-1024x522.png\" alt=\"La p\u00e1gina de pago alojada en Stripe.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163048\" class=\"wp-caption-text\">La p\u00e1gina de pago alojada en Stripe.<\/figcaption><\/figure>\n<p>Tras el pago, Stripe redirige de nuevo a tu aplicaci\u00f3n, permitiendo tareas posteriores al pago como confirmaciones y solicitudes de entrega. Para mayor fiabilidad, configura un webhook backend para actualizar Stripe, asegurando la retenci\u00f3n de los datos de pago incluso si los clientes cierran accidentalmente la p\u00e1gina despu\u00e9s del pago.<\/p>\n<p>Aunque es eficaz, este m\u00e9todo carece de flexibilidad en la personalizaci\u00f3n y el dise\u00f1o. Tambi\u00e9n puede ser complicado configurarlo correctamente para aplicaciones m\u00f3viles, donde una integraci\u00f3n nativa resultar\u00eda mucho m\u00e1s fluida.<\/p>\n<h4>Claves API<\/h4>\n<p>Cuando trabajes con la API de Stripe, necesitar\u00e1s acceder a claves API para que tus aplicaciones cliente y servidor interact\u00faen con el backend de Stripe. Puedes acceder a tus claves API de Stripe en <a href=\"https:\/\/dashboard.stripe.com\/test\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">tu panel de desarrollador de Stripe<\/a>. Este es el aspecto que tendr\u00eda<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/stripe-dashboard-api-keys-1024x522.png\" alt=\"El panel de Stripe mostrando las claves API\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163050\" class=\"wp-caption-text\">El panel de Stripe mostrando las claves API<\/figcaption><\/figure>\n<h3>\u00bfC\u00f3mo funcionan los pagos en Stripe?<\/h3>\n<p>Para entender c\u00f3mo funcionan los pagos en Stripe, tienes que entender a todas las partes implicadas. En cada transacci\u00f3n de pago intervienen cuatro partes interesadas:<\/p>\n<ol>\n<li><strong>Cliente<\/strong>: La persona que tiene la intenci\u00f3n de pagar por un servicio\/producto.<\/li>\n<li><strong>Comerciante<\/strong>: T\u00fa, el empresario, eres el responsable de recibir los pagos y vender los servicios\/productos.<\/li>\n<li><strong>Adquirente<\/strong>: Un banco que procesa los pagos en tu nombre (el comerciante) y dirige tu solicitud de pago a los bancos de tus clientes. Las entidades adquirentes pueden asociarse con terceros para ayudar a procesar los pagos.<\/li>\n<li><strong>Banco emisor<\/strong>: El banco que concede cr\u00e9ditos y emite tarjetas, y otros medios de pago a los consumidores.<\/li>\n<\/ol>\n<p>Aqu\u00ed puedes ver un flujo de pago t\u00edpico entre estas partes interesadas a muy alto nivel.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/how-online-payments-work-1024x201.png\" alt=\"C\u00f3mo funcionan los pagos online\" width=\"1024\" height=\"201\"><figcaption id=\"caption-attachment-163049\" class=\"wp-caption-text\">C\u00f3mo funcionan los pagos online<\/figcaption><\/figure>\n<p>El cliente hace saber al comerciante que est\u00e1 dispuesto a pagar. A continuaci\u00f3n, el comerciante env\u00eda los detalles relacionados con el pago a su banco adquirente, que recoge el pago del banco emisor del cliente y comunica al comerciante que el pago se ha realizado correctamente.<\/p>\n<p>Este es un resumen de muy alto nivel del proceso de pago. Como comerciante, s\u00f3lo tienes que preocuparte de recoger la intenci\u00f3n de pago, transmitirla al procesador de pagos y gestionar el resultado del pago. Sin embargo, como ya hemos comentado, existen dos formas de hacerlo.<\/p>\n<p>Cuando se crea una sesi\u00f3n de pago gestionada por Stripe en la que Stripe se encarga de la recogida de los datos de pago, el flujo t\u00edpico es el siguiente:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/stripe-hosted-payment-workflow-1024x749.png\" alt=\"El flujo de trabajo del pago gestionado por Stripe. \" width=\"1024\" height=\"749\"><figcaption id=\"caption-attachment-163051\" class=\"wp-caption-text\">El flujo de trabajo del pago gestionado por Stripe. (<strong>Fuente:<\/strong> <a href=\"https:\/\/stripe.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>Con los flujos de pago personalizados, realmente depende de ti. Puedes dise\u00f1ar la interacci\u00f3n entre tu cliente, el servidor, el cliente y la API de Stripe en funci\u00f3n de las necesidades de tu aplicaci\u00f3n. Puedes a\u00f1adir a este flujo de trabajo la recogida de direcciones, la generaci\u00f3n de facturas, la cancelaci\u00f3n, las pruebas gratuitas, etc., seg\u00fan necesites.<\/p>\n<p>Ahora que entiendes c\u00f3mo funcionan los pagos con Stripe, est\u00e1s listo para empezar a integrarlo en tu aplicaci\u00f3n Java.<\/p>\n<h2>Integraci\u00f3n de Stripe en una Aplicaci\u00f3n Spring Boot<\/h2>\n<p>Para comenzar la integraci\u00f3n de Stripe, crea una aplicaci\u00f3n frontend para interactuar con el backend Java e iniciar los pagos. En este tutorial, construir\u00e1s una app React para activar varios tipos de pago y suscripciones, de forma que obtengas una clara comprensi\u00f3n de sus mecanismos.<\/p>\n<p><strong>Nota<\/strong>: Este tutorial no cubre la construcci\u00f3n de un sitio de comercio electr\u00f3nico completo; su objetivo principal es guiarte a trav\u00e9s del sencillo proceso de integraci\u00f3n de Stripe en Spring Boot.<\/p>\n<h3>Configuraci\u00f3n de los proyectos frontend y backend<\/h3>\n<p>Crea un nuevo directorio y monta un proyecto React utilizando Vite ejecutando el siguiente comando:<\/p>\n<pre><code class=\"language-bash\">npm create vite@latest<\/code><\/pre>\n<p>Establece el nombre del proyecto como <strong>frontend<\/strong> (o el nombre que prefieras), framework como <strong>React<\/strong> y variante como <strong>TypeScript<\/strong>. Navega hasta el directorio del proyecto e instala Chakra UI para un andamiaje r\u00e1pido de los elementos de la interfaz de usuario ejecutando el siguiente comando:<\/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>Tambi\u00e9n instalar\u00e1s <code>react-router-dom<\/code> en tu proyecto para el enrutamiento del lado del cliente ejecutando el siguiente comando:<\/p>\n<pre><code class=\"language-bash\">npm i react-router-dom<\/code><\/pre>\n<p>Ahora, est\u00e1s listo para empezar a construir tu aplicaci\u00f3n frontend. Esta es la p\u00e1gina de inicio que vas a construir.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/completed-home-page-1024x522.png\" alt=\"La p\u00e1gina de inicio completada para la aplicaci\u00f3n del frontend.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163052\" class=\"wp-caption-text\">La p\u00e1gina de inicio completada para la aplicaci\u00f3n del frontend.<\/figcaption><\/figure>\n<p>Si haces clic en cualquier bot\u00f3n de esta p\u00e1gina, acceder\u00e1s a p\u00e1ginas de pago independientes con formularios de pago. Para empezar, crea una nueva carpeta llamada <strong>routes<\/strong> en tu directorio <strong>frontend\/src<\/strong>. Dentro de esta carpeta, crea un archivo <strong>Home.tsx<\/strong>. Este archivo contendr\u00e1 el c\u00f3digo de la ruta de inicio de tu aplicaci\u00f3n (<code>\/<\/code>). Pega el siguiente c\u00f3digo en el archivo:<\/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>Para habilitar la navegaci\u00f3n en tu aplicaci\u00f3n, actualiza tu archivo <strong>App.tsx<\/strong> para configurar la clase <code>RouteProvider<\/code> de <code>react-router-dom<\/code>.<\/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>Ejecuta el comando <code>npm run dev<\/code> para previsualizar tu aplicaci\u00f3n en <a href=\"https:\/\/localhost:5173\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/localhost:5173<\/a>.<\/p>\n<p>Esto completa la configuraci\u00f3n inicial necesaria para el frontend de la app. A continuaci\u00f3n, crea una aplicaci\u00f3n backend utilizando Spring Boot. Para inicializar la aplicaci\u00f3n, puedes utilizar el sitio web <a href=\"https:\/\/start.spring.io\/\" target=\"_blank\" rel=\"noopener noreferrer\">spring initializr<\/a> (si tu IDE admite la creaci\u00f3n de aplicaciones Spring, no necesitas utilizar el sitio web).<\/p>\n<p>IntelliJ IDEA soporta la creaci\u00f3n de aplicaciones Spring Boot. Empieza eligiendo la opci\u00f3n <strong>New Project<\/strong>\u00a0en IntelliJ IDEA. A continuaci\u00f3n, elige <strong>Spring Initializr<\/strong> en el panel izquierdo. Introduce los detalles de tu proyecto backend: name (<strong>backend<\/strong>), location (directorio <strong>stripe-payments-java<\/strong>), language (<strong>Java<\/strong>) y type (<strong>Maven<\/strong>). Para los nombres de grupo y artefacto, utiliza <strong>com.kinsta.stripe-java<\/strong> y backend <strong>,<\/strong> respectivamente.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/idea-new-project-dialog.png\" alt=\"El di\u00e1logo de nuevo proyecto de IDEA.\" width=\"814\" height=\"727\"><figcaption id=\"caption-attachment-163053\" class=\"wp-caption-text\">El di\u00e1logo de nuevo proyecto de IDEA.<\/figcaption><\/figure>\n<p>Haz clic en el bot\u00f3n <strong>Next<\/strong>. A continuaci\u00f3n, a\u00f1ade dependencias a tu proyecto eligiendo <strong>Spring Web<\/strong> en el desplegable <strong>Web<\/strong> del panel de dependencias y haz clic en el bot\u00f3n <strong>Create<\/strong>.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/idea-new-project-dialog-dependencies.png\" alt=\"Eligiendo las dependencias.\" width=\"814\" height=\"727\"><figcaption id=\"caption-attachment-163054\" class=\"wp-caption-text\">Eligiendo las dependencias.<\/figcaption><\/figure>\n<p>Esto crear\u00e1 el proyecto Java y lo abrir\u00e1 en tu IDE. Ahora puedes proceder a crear los distintos flujos de pago utilizando Stripe.<\/p>\n<h2>Aceptar Pagos Online para la Compra de Productos<\/h2>\n<p>La funcionalidad m\u00e1s importante y utilizada de Stripe es aceptar pagos \u00fanicos de los clientes. En esta secci\u00f3n, aprender\u00e1s dos formas de integrar el procesamiento de pagos en tu aplicaci\u00f3n con Stripe.<\/p>\n<h3>Hosted Checkout (Pago Alojado)<\/h3>\n<p>En primer lugar, construyes una p\u00e1gina de pago que activa un flujo de trabajo de pago alojado en el que s\u00f3lo activas un pago desde tu aplicaci\u00f3n frontend. Entonces Stripe se encarga de recopilar los datos de la tarjeta del cliente y de cobrar el pago, y s\u00f3lo comparte el resultado de la operaci\u00f3n de pago al final.<\/p>\n<p>Este es el aspecto que tendr\u00eda la p\u00e1gina de pago:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/completed-hosted-checkout-page-1024x506.png\" alt=\"La p\u00e1gina de pago alojada finalizada.\" width=\"1024\" height=\"506\"><figcaption id=\"caption-attachment-163055\" class=\"wp-caption-text\">La p\u00e1gina de pago alojada finalizada.<\/figcaption><\/figure>\n<p>Esta p\u00e1gina tiene tres componentes principales: <code>CartItem<\/code> &#8211; representa cada art\u00edculo del carrito; <code>TotalFooter<\/code> &#8211; muestra el importe total; <code>CustomerDetails<\/code> &#8211; recoge los datos del cliente. Puedes reutilizar estos componentes para crear formularios de pago para otros escenarios de este art\u00edculo, como el pago integrado y las suscripciones.<\/p>\n<h4>Construir el frontend<\/h4>\n<p>Crea una carpeta <strong>components<\/strong> en tu directorio <strong>frontend\/src<\/strong>. En la carpeta <strong>components<\/strong>, crea un nuevo archivo <strong>CartItem.tsx<\/strong> y pega el siguiente c\u00f3digo:<\/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>El c\u00f3digo anterior define dos interfaces que se utilizar\u00e1n como tipos para las propiedades pasadas al componente. El tipo <code>ItemData<\/code> se exporta para reutilizarlo en otros componentes.<\/p>\n<p>El c\u00f3digo devuelve el dise\u00f1o de un componente de art\u00edculo de carrito. Utiliza las propiedades proporcionadas para representar el elemento en la pantalla.<\/p>\n<p>A continuaci\u00f3n, crea un archivo <strong>TotalFooter.tsx<\/strong> en el directorio de <strong>components<\/strong> y pega el siguiente c\u00f3digo:<\/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>El componente <code>TotalFooter<\/code> muestra el valor total del carrito y utiliza el valor <code>mode<\/code> para <a href=\"https:\/\/kinsta.com\/es\/blog\/react-renderizado-condicional\/\">mostrar condicionalmente<\/a> un texto espec\u00edfico.<\/p>\n<p>Por \u00faltimo, crea el componente <code>CustomerDetails.tsx<\/code> y pega el siguiente c\u00f3digo:<\/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>El c\u00f3digo anterior muestra un formulario con dos campos de entrada, para recoger el nombre y el correo electr\u00f3nico del usuario. Cuando se pulsa el bot\u00f3n de <strong>Checkout<\/strong>, se invoca el m\u00e9todo <code>initiatePayment<\/code> para enviar la solicitud de pago al backend.<\/p>\n<p>Solicita el punto final que has pasado al componente y env\u00eda la informaci\u00f3n del cliente y los art\u00edculos del carrito como parte de la solicitud, luego redirige al usuario a la URL recibida del servidor. Esta URL llevar\u00e1 al usuario a una p\u00e1gina de pago alojada en el servidor de Stripe. M\u00e1s adelante te ocupar\u00e1s de la construcci\u00f3n de esta URL.<\/p>\n<p><strong>Nota:<\/strong> Este componente utiliza la variable de entorno <code>VITE_SERVER_BASE_URL<\/code> para la URL del servidor backend. Establ\u00e9cela creando un archivo <strong>.env<\/strong> en la ra\u00edz de tu proyecto:<\/p>\n<pre><code class=\"language-bash\">VITE_SERVER_BASE_URL=<a href=\"http:\/\/localhost:8080\">http:\/\/localhost:8080<\/a><\/code><\/pre>\n<p>Todos los componentes han sido creados. Ahora, vamos a construir la ruta de pago alojado utilizando los componentes. Para ello, crea un nuevo archivo <strong>HostedCheckout.tsx<\/strong> en tu carpeta de <strong>routes<\/strong> con el siguiente c\u00f3digo:<\/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>Esta ruta utiliza los tres componentes que acabas de construir para montar una pantalla de pago. Todos los modos de los componentes est\u00e1n configurados como <strong>checkout<\/strong>, y se proporciona el endpoint <code>\/checkout\/hosted<\/code> al componente del formulario para iniciar la solicitud de checkout con precisi\u00f3n.<\/p>\n<p>El componente utiliza un objeto <code>Products<\/code> para rellenar la matriz de elementos. En el mundo real, estos datos proceden de la API de tu carrito, y contienen los art\u00edculos seleccionados por el usuario. Sin embargo, para este tutorial, una lista est\u00e1tica de un script rellena el array. Define la matriz creando un archivo <strong>data.ts<\/strong> en la ra\u00edz de tu proyecto frontend y almacenando el siguiente c\u00f3digo en \u00e9l:<\/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>Este archivo define dos elementos en la matriz de productos que se muestran en el carrito. Puedes modificar tranquilamente los valores de los productos.<\/p>\n<p>Como \u00faltimo paso en la construcci\u00f3n del frontend, crea dos nuevas rutas para gestionar el \u00e9xito y el fracaso. La p\u00e1gina de pago alojada en Stripe redirigir\u00e1 a los usuarios a tu aplicaci\u00f3n por estas dos rutas en funci\u00f3n del resultado de la transacci\u00f3n. Stripe tambi\u00e9n proporcionar\u00e1 a tus rutas la carga \u00fatil relacionada con la transacci\u00f3n, como el ID de sesi\u00f3n de pago, que puedes utilizar para <a href=\"https:\/\/stripe.com\/docs\/api\/checkout\/sessions\/retrieve\" target=\"_blank\" rel=\"noopener noreferrer\">recuperar el objeto de sesi\u00f3n de pago correspondiente<\/a> y acceder a los datos relacionados con el pago, como el m\u00e9todo de pago, los detalles de la factura, etc.<\/p>\n<p>Para ello, crea un archivo <strong>Success.tsx<\/strong> en el directorio <strong>src\/routes<\/strong> y guarda en \u00e9l el siguiente c\u00f3digo:<\/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>Al renderizarse, este componente muestra el mensaje \u00ab\u00a1Success!\u00bb e imprime en pantalla cualquier par\u00e1metro de consulta de la URL. Tambi\u00e9n incluye un bot\u00f3n para redirigir a los usuarios a la p\u00e1gina de inicio de la aplicaci\u00f3n.<\/p>\n<p>Cuando construyas aplicaciones para el mundo real, esta p\u00e1gina es donde gestionar\u00e1s las transacciones no cr\u00edticas del lado de la aplicaci\u00f3n que dependan del \u00e9xito de la transacci\u00f3n en cuesti\u00f3n. Por ejemplo, si est\u00e1s creando una p\u00e1gina de pago para una tienda online, podr\u00edas utilizar esta p\u00e1gina para mostrar una confirmaci\u00f3n al usuario y un tiempo estimado de entrega de los productos adquiridos.<\/p>\n<p>A continuaci\u00f3n, crea un archivo <strong>Failure.tsx<\/strong> con el siguiente c\u00f3digo:<\/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>Este componente es similar al de <strong>Success.tsx<\/strong> y muestra el mensaje \u00ab\u00a1Failure!\u00bb cuando se procesa.<\/p>\n<p><aside role=\"note\" class=\"wp-block-kinsta-notice is-style-info\">\n            <h3>Info<\/h3>\n        <p>Evita pasar informaci\u00f3n cr\u00edtica o realizar operaciones cr\u00edticas tanto en la p\u00e1gina de \u00e9xito como en la de fallo, ya que es posible que el cliente nunca llegue a ninguna de las dos p\u00e1ginas si cierra la pesta\u00f1a del navegador o pierde la conectividad.<\/p>\n<\/aside>\n.<\/p>\n<p>Para tareas esenciales como la entrega del producto, el env\u00edo de correos electr\u00f3nicos o cualquier parte cr\u00edtica de tu flujo de compra, utiliza <a href=\"https:\/\/stripe.com\/docs\/webhooks\" target=\"_blank\" rel=\"noopener noreferrer\">webhooks<\/a>. Los webhooks son rutas API en tu servidor que Stripe puede invocar cuando se produce una transacci\u00f3n.<\/p>\n<p>El webhook recibe todos los detalles de la transacci\u00f3n (a trav\u00e9s del objeto <code>CheckoutSession<\/code> ), lo que te permite registrarla en la base de datos de tu aplicaci\u00f3n y desencadenar los correspondientes flujos de trabajo de \u00e9xito o fracaso. Como tu servidor est\u00e1 siempre accesible para Stripe, no se pierde ninguna transacci\u00f3n, lo que garantiza la funcionalidad consistente de tu tienda online.<\/p>\n<p>Por \u00faltimo, actualiza el archivo <strong>App.tsx<\/strong> para que tenga este aspecto:<\/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>Esto asegurar\u00e1 que los componentes <code>Success<\/code> y <code>Failure<\/code> se rendericen en las rutas <code>\/success<\/code> y <code>\/failure<\/code>, respectivamente.<\/p>\n<p>Esto completa la configuraci\u00f3n del frontend. A continuaci\u00f3n, configura el backend para crear el endpoint <code>\/checkout\/hosted<\/code>.<\/p>\n<h4>Construir el backend<\/h4>\n<p>Abre el proyecto del backend e instala el SDK de Stripe a\u00f1adiendo las siguientes l\u00edneas en la matriz de dependencias de tu archivo <strong>pom.xml<\/strong>:<\/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>A continuaci\u00f3n, <a href=\"https:\/\/www.jetbrains.com\/help\/idea\/delegate-build-and-run-actions-to-maven.html\" target=\"_blank\" rel=\"noopener noreferrer\">carga los cambios de Maven<\/a> en tu proyecto para instalar las dependencias. Si tu IDE no lo permite a trav\u00e9s de la interfaz de usuario, ejecuta el comando <code>maven dependency:resolve<\/code> o <code>maven install<\/code>. Si no tienes la CLI <code>maven<\/code>, utiliza la envoltura <code>mvnw<\/code> de Spring initializr al crear el proyecto.<\/p>\n<p>Una vez instaladas las dependencias, crea un nuevo controlador REST para gestionar las solicitudes HTTP entrantes de tu aplicaci\u00f3n backend. Para ello, crea un archivo <strong>PaymentController.java<\/strong> en el directorio <strong>src\/main\/java\/com\/kinsta\/stripe-java\/backend<\/strong> y a\u00f1ade el siguiente c\u00f3digo:<\/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>El c\u00f3digo anterior importa las dependencias esenciales de Stripe y establece la clase <code>PaymentController<\/code>. Esta clase lleva dos anotaciones: <code>@RestController<\/code> y <code>@CrossOrigin<\/code>. La anotaci\u00f3n <code>@RestController<\/code> indica a Spring Boot que trate esta clase como un controlador, y sus m\u00e9todos pueden utilizar ahora las anotaciones <code>@Mapping<\/code> para gestionar las solicitudes HTTP entrantes.<\/p>\n<p>La anotaci\u00f3n <code>@CrossOrigin<\/code> marca todos los endpoints definidos en esta clase como abiertos a todos los or\u00edgenes seg\u00fan las reglas CORS. Sin embargo, esta pr\u00e1ctica se desaconseja en producci\u00f3n debido a las posibles vulnerabilidades de seguridad de varios dominios de Internet.<\/p>\n<p>Para obtener resultados \u00f3ptimos, es aconsejable alojar los servidores backend y frontend en el mismo dominio para eludir los problemas CORS. Alternativamente, si esto no es factible, puedes especificar el dominio de tu cliente frontend (que env\u00eda peticiones al servidor backend) utilizando la anotaci\u00f3n <code>@CrossOrigin<\/code>, de la siguiente manera:<\/p>\n<pre><code class=\"language-java\">@CrossOrigin(origins = \"http:\/\/frontend.com\")<\/code><\/pre>\n<p>La clase <code>PaymentController<\/code> extraer\u00e1 la clave de la API de Stripe de las variables de entorno para proporcion\u00e1rsela posteriormente al SDK de Stripe. Cuando ejecutes la aplicaci\u00f3n, deber\u00e1s proporcionar tu clave de la API de Stripe a la aplicaci\u00f3n a trav\u00e9s de variables de entorno.<\/p>\n<p>Localmente, puedes crear una nueva variable de entorno en tu sistema, ya sea temporalmente (a\u00f1adiendo una frase <code>KEY=VALUE<\/code> antes del comando utilizado para iniciar tu servidor de desarrollo) o permanentemente (actualizando los archivos de configuraci\u00f3n de tu terminal o estableciendo una variable de entorno en el panel de control en Windows).<\/p>\n<p>En entornos de producci\u00f3n, tu proveedor de despliegue (como Kinsta) te proporcionar\u00e1 una opci\u00f3n independiente para rellenar las variables de entorno utilizadas por tu aplicaci\u00f3n.<\/p>\n<p>Si utilizas IntelliJ IDEA (o un IDE similar), haz clic en <strong>Run<\/strong><strong>\u00a0Configurations<\/strong> en la parte superior derecha del IDE y haz clic en la opci\u00f3n <strong>Edit Configurations&#8230;<\/strong> de la lista desplegable que se abre para actualizar tu comando de ejecuci\u00f3n y establecer la variable de entorno.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/run-debug-configurations-dialog-box-1024x420.png\" alt=\"Abriendo el cuadro de di\u00e1logo de configuraciones de ejecuci\u00f3n\/depuraci\u00f3n.\" width=\"1024\" height=\"420\"><figcaption id=\"caption-attachment-163056\" class=\"wp-caption-text\">Abriendo el cuadro de di\u00e1logo de configuraciones de ejecuci\u00f3n\/depuraci\u00f3n.<\/figcaption><\/figure>\n<p>Se abrir\u00e1 un cuadro de di\u00e1logo en el que puedes proporcionar las variables de entorno para tu aplicaci\u00f3n utilizando el campo <strong>Environment variables<\/strong>. Introduce la variable de entorno <code>STRIPE_API_KEY<\/code> en el formato <code>VAR1=VALUE<\/code>. Puedes encontrar tu clave API en el <a href=\"https:\/\/dashboard.stripe.com\/account\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">sitio web de desarrolladores de Stripe<\/a>. Debes proporcionar el valor de la <strong>Clave<\/strong> <strong>Secreta<\/strong> desde esta p\u00e1gina.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/stripe-dashboard-showing-api-keys-1024x504.png\" alt=\"El panel de control de Stripe mostrando las claves API.\" width=\"1024\" height=\"504\"><figcaption id=\"caption-attachment-163057\" class=\"wp-caption-text\">El panel de control de Stripe mostrando las claves API.<\/figcaption><\/figure>\n<p>Si a\u00fan no lo has hecho, <a href=\"https:\/\/dashboard.stripe.com\/register\" target=\"_blank\" rel=\"noopener noreferrer\">crea una nueva cuenta de Stripe<\/a> para acceder a las claves API.<\/p>\n<p>Una vez que hayas configurado la clave API, procede a construir el endpoint. Este endpoint recopilar\u00e1 los datos del cliente (nombre y correo electr\u00f3nico), crear\u00e1 un perfil de cliente para ellos en Stripe si a\u00fan no existe uno, y crear\u00e1 una <a href=\"https:\/\/stripe.com\/docs\/payments\/checkout\/how-checkout-works\" target=\"_blank\" rel=\"noopener noreferrer\">Sesi\u00f3n de Pago<\/a> para permitir a los usuarios pagar los art\u00edculos del carrito.<\/p>\n<p>Este es el aspecto del c\u00f3digo del m\u00e9todo <code>hostedCheckout<\/code>:<\/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>Al crear la sesi\u00f3n de pago, el c\u00f3digo utiliza el nombre del producto recibido del cliente, pero no utiliza los detalles del precio de la solicitud. Este enfoque evita la posible manipulaci\u00f3n de precios por parte del cliente, donde los actores maliciosos podr\u00edan enviar precios reducidos en la solicitud de pago para pagar menos por productos y servicios.<\/p>\n<p>Para evitarlo, el m\u00e9todo <code>hostedCheckout<\/code> consulta tu base de datos de productos (a trav\u00e9s de <code>ProductDAO<\/code>) para recuperar el precio correcto del art\u00edculo.<\/p>\n<p>Adem\u00e1s, Stripe ofrece varias clases <code>Builder<\/code> que siguen el patr\u00f3n de dise\u00f1o constructor. Estas clases ayudan a crear objetos par\u00e1metro para las peticiones de Stripe. El fragmento de c\u00f3digo proporcionado tambi\u00e9n hace referencia a variables de entorno para obtener la URL de la aplicaci\u00f3n cliente. Esta URL es necesaria para que el objeto de sesi\u00f3n de pago redirija adecuadamente tras un pago correcto o fallido.<\/p>\n<p>Para ejecutar este c\u00f3digo, establece la URL de la aplicaci\u00f3n cliente mediante variables de entorno, de forma similar a como se proporcion\u00f3 la clave API de Stripe. Como la aplicaci\u00f3n cliente se ejecuta a trav\u00e9s de Vite, la URL de la aplicaci\u00f3n local debe ser http:\/\/localhost:5173. Incluye esto en tus variables de entorno a trav\u00e9s de tu IDE, terminal o panel de control del sistema.<\/p>\n<pre><code class=\"language-bash\">CLIENT_BASE_URL=http:\/\/localhost:5173<\/code><\/pre>\n<p>Adem\u00e1s, proporciona a la app un <code>ProductDAO<\/code> desde el que buscar los precios de los productos. El Objeto de Acceso a Datos (DAO -Data access object) interact\u00faa con fuentes de datos (como bases de datos) para acceder a los datos relacionados con la app. Aunque configurar una base de datos de productos quedar\u00eda fuera del alcance de este tutorial, una implementaci\u00f3n sencilla que puedes hacer ser\u00eda a\u00f1adir un nuevo archivo <strong>ProductDAO.java<\/strong> en el mismo directorio que el <strong>PaymentController.java<\/strong> y pegar el siguiente c\u00f3digo:<\/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>Esto inicializar\u00e1 una matriz de productos y te permitir\u00e1 consultar los datos del producto utilizando su identificador (ID). Tambi\u00e9n tendr\u00e1s que crear un Objeto de Transferencia de Datos (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Data_transfer_object\" target=\"_blank\" rel=\"noopener noreferrer\">DTO<\/a> &#8211; Data transfer object) para permitir que Spring Boot serialice autom\u00e1ticamente la carga \u00fatil entrante desde el cliente y te presente un objeto sencillo para acceder a los datos. Para ello, crea un nuevo archivo <strong>RequestDTO.java<\/strong> y pega el siguiente c\u00f3digo:<\/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>Este archivo define un <a href=\"https:\/\/en.wikipedia.org\/wiki\/Plain_old_Java_object\" target=\"_blank\" rel=\"noopener noreferrer\">POJO<\/a> que contiene el nombre del cliente, su correo electr\u00f3nico y la lista de art\u00edculos que est\u00e1 comprando.<\/p>\n<p>Por \u00faltimo, implementa el m\u00e9todo <code>CustomerUtil.findOrCreateCustomer()<\/code> para crear el objeto Cliente en Stripe si a\u00fan no existe. Para ello, crea un archivo con el nombre <code>CustomerUtil<\/code> y a\u00f1\u00e1dele el siguiente c\u00f3digo:<\/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>Esta clase tambi\u00e9n contiene otro m\u00e9todo <code>findCustomerByEmail<\/code> que te permite buscar clientes en Stripe utilizando sus direcciones de correo electr\u00f3nico. La <a href=\"https:\/\/stripe.com\/docs\/api\/customers\/search\">API de b\u00fasqueda de clientes<\/a>\u00a0se utiliza para buscar los registros de clientes en la base de datos de Stripe y <a href=\"https:\/\/stripe.com\/docs\/api\/customers\/create\" target=\"_blank\" rel=\"noopener noreferrer\">la API de creaci\u00f3n de clientes<\/a> se utiliza para crear los registros de clientes seg\u00fan sea necesario.<\/p>\n<p>Esto completa la configuraci\u00f3n del backend necesaria para el flujo de pago alojado. Ahora puedes probar la aplicaci\u00f3n ejecutando las aplicaciones frontend y backend en sus IDEs o terminales independientes. Este es el aspecto que tendr\u00eda el flujo de \u00e9xito:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/successful-hosted-checkout-flow.gif\" alt=\"Un hosted checkout con \u00e9xito.\" width=\"1912\" height=\"1062\"><figcaption id=\"caption-attachment-163058\" class=\"wp-caption-text\">Un hosted checkout con \u00e9xito.<\/figcaption><\/figure>\n<p>Cuando pruebes integraciones con Stripe, siempre puedes utilizar los siguientes datos de tarjeta para simular transacciones con tarjeta:<\/p>\n<p><strong>N\u00famero de<\/strong> <strong>tarjeta<\/strong>: 4111 1111 1111 1111<br \/>\n<strong>Mes y a\u00f1o de caducidad<\/strong>: 12 \/ 25<br \/>\n<strong>CVV<\/strong>: Cualquier n\u00famero de tres d\u00edgitos<br \/>\n<strong>Nombre en la<\/strong> <strong>tarjeta<\/strong>: Cualquier nombre<\/p>\n<p>Si eliges cancelar la transacci\u00f3n en lugar de pagar, as\u00ed es como ser\u00eda el flujo de fallo:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/failed-hosted-checkout-flow.gif\" alt=\"Un flujo de pago alojado fallido.\" width=\"1912\" height=\"1062\"><figcaption id=\"caption-attachment-163059\" class=\"wp-caption-text\">Un flujo de pago alojado fallido.<\/figcaption><\/figure>\n<p>Esto completa la configuraci\u00f3n de una experiencia de pago alojada en Stripe integrada en tu aplicaci\u00f3n. Puedes consultar <a href=\"https:\/\/stripe.com\/docs\/payments\/checkout\/how-checkout-works\" target=\"_blank\" rel=\"noopener noreferrer\">la documentaci\u00f3n de Stripe<\/a> para obtener m\u00e1s informaci\u00f3n sobre c\u00f3mo personalizar tu p\u00e1gina de pago, recopilar m\u00e1s datos del cliente, etc.<\/p>\n<h3>Integrated Checkout<\/h3>\n<p>Una experiencia con integrated checkout consiste en crear un flujo de pago que no redirija a tus usuarios fuera de tu aplicaci\u00f3n (como ocurr\u00eda en el flujo de hosted checkout) y que muestre el formulario de pago en tu propia aplicaci\u00f3n.<\/p>\n<p>Construir una experiencia de pago integrada significa manejar los detalles de pago de los clientes, lo que implica informaci\u00f3n sensible como n\u00fameros de tarjetas de cr\u00e9dito, ID de Google Pay, etc. No todas las aplicaciones est\u00e1n dise\u00f1adas para gestionar estos datos de forma segura.<\/p>\n<p>Para eliminar la carga de cumplir normas como la PCI-DSS, Stripe proporciona <a href=\"https:\/\/stripe.com\/payments\/elements\" target=\"_blank\" rel=\"noopener noreferrer\">elementos<\/a> que puedes utilizar dentro de la aplicaci\u00f3n para recopilar datos de pago, dejando que Stripe gestione la seguridad y procese los pagos de forma segura.<\/p>\n<h4>Construir el Frontend<\/h4>\n<p>Para empezar, instala el <a href=\"https:\/\/www.npmjs.com\/package\/@stripe\/react-stripe-js\" target=\"_blank\" rel=\"noopener noreferrer\">SDK React de Stripe<\/a> en tu aplicaci\u00f3n frontend para acceder a los elementos de Stripe ejecutando el siguiente comando en el directorio de tu frontend:<\/p>\n<pre><code class=\"language-bash\">npm i @stripe\/react-stripe-js @stripe\/stripe-js<\/code><\/pre>\n<p>A continuaci\u00f3n, crea un nuevo archivo llamado <strong>IntegratedCheckout.tsx<\/strong> en tu directorio <strong>frontend\/src\/routes<\/strong> y guarda el siguiente c\u00f3digo en \u00e9l:<\/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>Este archivo define dos componentes, <code>IntegratedCheckout<\/code> y <code>CheckoutForm<\/code>. El <code>CheckoutForm<\/code> define un formulario sencillo con un <code>PaymentElement<\/code> de Stripe que recoge los datos de pago de los clientes y un bot\u00f3n <strong>Pagar<\/strong> que desencadena una solicitud de cobro del pago.<\/p>\n<p>Este componente tambi\u00e9n llama al hook <code>useStripe()<\/code> y <code>useElements()<\/code> para crear una instancia del SDK de Stripe que puedes utilizar para crear solicitudes de pago. Una vez que haces clic en el bot\u00f3n <strong>Pagar<\/strong>, se llama al m\u00e9todo <code>stripe.confirmPayment()<\/code> del SDK de Stripe que recoge los datos de pago del usuario de la instancia de elementos y los env\u00eda al backend de Stripe con una URL de \u00e9xito a la que redirigir si la transacci\u00f3n se realiza correctamente.<\/p>\n<p>El formulario de pago se ha separado del resto de la p\u00e1gina porque los hooks <code>useStripe()<\/code> y <code>useElements()<\/code> necesitan ser llamados desde el contexto de un proveedor <code>Elements<\/code>, lo que se ha hecho en la sentencia return de <code>IntegratedCheckout<\/code>. Si trasladaras las llamadas al gancho Stripe al componente <code>IntegratedCheckout<\/code> directamente, quedar\u00edan fuera del \u00e1mbito del proveedor <code>Elements<\/code> y, por tanto, no funcionar\u00edan.<\/p>\n<p>El componente <code>IntegratedCheckout<\/code> reutiliza los componentes <code>CartItem<\/code> y <code>TotalFooter<\/code> para mostrar los art\u00edculos del carrito y el importe total. Tambi\u00e9n muestra dos campos de entrada para recoger la informaci\u00f3n del cliente y un bot\u00f3n <strong>Iniciar pago<\/strong> que env\u00eda una solicitud al servidor Java backend para crear la clave secreta del cliente utilizando los datos del cliente y del carrito. Una vez recibida la clave secreta del cliente, se muestra <code>CheckoutForm<\/code>, que se encarga de recoger los datos de pago del cliente.<\/p>\n<p>Aparte de eso, <code>useEffect<\/code> se utiliza para llamar al m\u00e9todo <code>loadStripe<\/code>. Este efecto s\u00f3lo se ejecuta una vez cuando se renderiza el componente, para que el SDK de Stripe no se cargue varias veces cuando se actualicen los estados internos del componente.<\/p>\n<p>Para ejecutar el c\u00f3digo anterior, tambi\u00e9n tendr\u00e1s que a\u00f1adir dos nuevas variables de entorno a tu proyecto frontend: <code>VITE_STRIPE_API_KEY<\/code> y <code>VITE_CLIENT_BASE_URL<\/code>. La variable clave API de Stripe contendr\u00e1 la clave API publicable del <a href=\"https:\/\/dashboard.stripe.com\/test\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">panel de control de Stripe<\/a>, y la variable URL base del cliente contendr\u00e1 el enlace a la aplicaci\u00f3n cliente (que es la propia aplicaci\u00f3n del frontend) para que pueda pasarse al SDK de Stripe para gestionar las redirecciones de \u00e9xito y fracaso.<\/p>\n<p>Para ello, a\u00f1ade el siguiente c\u00f3digo a tu archivo <strong>.env<\/strong> en el directorio del frontend:<\/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>Por \u00faltimo, actualiza el archivo <strong>App.tsx<\/strong> para incluir el componente <code>IntegratedCheckout<\/code> en la ruta <code>\/integrated-checkout<\/code> de la aplicaci\u00f3n del frontend. A\u00f1ade el siguiente c\u00f3digo en el array pasado a la llamada <code>createBrowserRouter<\/code> en el componente <code>App<\/code>:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/integrated-checkout',\n            element: (\n                &lt;IntegratedCheckout\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Esto completa la configuraci\u00f3n necesaria en el frontend. A continuaci\u00f3n, crea una nueva ruta en tu servidor backend que cree la clave secreta de cliente necesaria para gestionar las sesiones de pago integradas en tu aplicaci\u00f3n frontend.<\/p>\n<h4>Construir el backend<\/h4>\n<p>Para garantizar que los atacantes no abusan de la integraci\u00f3n del frontend (ya que el c\u00f3digo del frontend es m\u00e1s f\u00e1cil de descifrar que el del backend), Stripe requiere que generes un secreto de cliente \u00fanico en tu servidor backend y verifica cada solicitud de pago integrado con el secreto de cliente generado en el backend para asegurarse de que es realmente tu aplicaci\u00f3n la que est\u00e1 intentando cobrar los pagos. Para ello, tienes que configurar otra ruta en el backend que cree secretos de cliente basados en la informaci\u00f3n del cliente y del carrito.<\/p>\n<p>Para crear la clave secreta de cliente en tu servidor, crea un nuevo m\u00e9todo en tu clase <code>PaymentController<\/code> con el nombre <code>integratedCheckout<\/code> y guarda en \u00e9l el siguiente c\u00f3digo:<\/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>De forma similar a c\u00f3mo se construy\u00f3 la sesi\u00f3n de pago utilizando una clase constructora que acepta la configuraci\u00f3n para la solicitud de pago, el flujo de pago integrado requiere que construyas una sesi\u00f3n de pago con el importe, la moneda y los m\u00e9todos de pago. A diferencia de la sesi\u00f3n de pago, no puedes asociar <a href=\"https:\/\/stripe.com\/docs\/api\/invoices\/line_item\" target=\"_blank\" rel=\"noopener noreferrer\">partidas<\/a> a una sesi\u00f3n de pago a menos que crees una factura, lo que aprender\u00e1s en una secci\u00f3n posterior del tutorial.<\/p>\n<p>Como no est\u00e1s pasando las partidas al constructor de la sesi\u00f3n de pago, tienes que calcular manualmente el importe total de los art\u00edculos del carrito y enviar el importe al backend de Stripe. Utiliza tu <code>ProductDAO<\/code> para encontrar y a\u00f1adir los precios de cada producto del carrito.<\/p>\n<p>Para ello, define un nuevo m\u00e9todo <code>calculateOrderAmount<\/code> y a\u00f1ade el siguiente c\u00f3digo en \u00e9l:<\/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>Esto deber\u00eda ser suficiente para configurar el flujo de pago integrado tanto en el frontend como en el backend. Puedes reiniciar los servidores de desarrollo para el servidor y el cliente y probar el nuevo flujo de pago integrado en la aplicaci\u00f3n del frontend. Este es el aspecto que tendr\u00e1 el flujo integrado:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/integrated-checkout-flow.gif\" alt=\"Integrated checkout.\" width=\"1912\" height=\"1062\"><figcaption id=\"caption-attachment-163060\" class=\"wp-caption-text\">Integrated checkout.<\/figcaption><\/figure>\n<p>Esto completa un flujo de pago integrado b\u00e1sico en tu aplicaci\u00f3n. Ahora puedes seguir explorando la documentaci\u00f3n de Stripe para <a href=\"https:\/\/stripe.com\/docs\/payments\/customize-payment-element\" target=\"_blank\" rel=\"noopener noreferrer\">personalizar los m\u00e9todos de pago<\/a> o integrar m\u00e1s componentes que te ayuden con otras operaciones, como la <a href=\"https:\/\/stripe.com\/docs\/elements\/address-element\" target=\"_blank\" rel=\"noopener noreferrer\">recopilaci\u00f3n de direcciones<\/a>, las <a href=\"https:\/\/stripe.com\/docs\/payments\/elements\/link-authentication-element\" target=\"_blank\" rel=\"noopener noreferrer\">solicitudes de pago<\/a>, la <a href=\"https:\/\/stripe.com\/docs\/stripe-js\/elements\/payment-request-button\" target=\"_blank\" rel=\"noopener noreferrer\">integraci\u00f3n de enlaces<\/a>, \u00a1y mucho m\u00e1s!<\/p>\n<h2>Configurar Suscripciones para Servicios Recurrentes<\/h2>\n<p>Una oferta habitual de las tiendas online hoy en d\u00eda es la suscripci\u00f3n. Tanto si est\u00e1s creando una tienda de servicios, como si ofreces un producto digital peri\u00f3dicamente, una suscripci\u00f3n es la soluci\u00f3n perfecta para dar a tus clientes acceso peri\u00f3dico a tu servicio por una peque\u00f1a cuota en comparaci\u00f3n con una compra \u00fanica.<\/p>\n<p>Stripe puede ayudarte a configurar y cancelar suscripciones f\u00e1cilmente. Tambi\u00e9n puedes ofrecer pruebas gratuitas como parte de tu suscripci\u00f3n para que los usuarios puedan probar tu oferta antes de comprometerse con ella.<\/p>\n<h3>Configurar una nueva suscripci\u00f3n<\/h3>\n<p>Configurar una nueva suscripci\u00f3n es sencillo utilizando el flujo de hosted checkout. S\u00f3lo tendr\u00e1s que cambiar algunos par\u00e1metros al crear la solicitud de pago y crear una nueva p\u00e1gina (reutilizando los componentes existentes) para mostrar una p\u00e1gina de pago para una nueva suscripci\u00f3n. Para empezar, crea un archivo <strong>NewSubscription.tsx<\/strong> en la carpeta de <strong>components<\/strong> del frontend. Pega en \u00e9l el siguiente c\u00f3digo:<\/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>En el c\u00f3digo anterior, los datos del carrito se toman del archivo <strong>data.ts<\/strong>, y s\u00f3lo contiene un elemento para simplificar el proceso. En situaciones reales, puedes tener varios art\u00edculos como parte de un pedido de suscripci\u00f3n.<\/p>\n<p>Para renderizar este componente en la ruta correcta, a\u00f1ade el siguiente c\u00f3digo en la matriz pasada a la llamada <code>createBrowserRouter<\/code> en el componente <strong>App.tsx<\/strong>:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/new-subscription',\n            element: (\n                &lt;NewSubscription\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Esto completa la configuraci\u00f3n necesaria en el frontend. En el backend, crea una nueva ruta <code>\/subscription\/new<\/code> para crear una nueva sesi\u00f3n de pago alojada para un producto de suscripci\u00f3n. Crea un m\u00e9todo <code>newSubscription<\/code> en el directorio <strong>backend\/src\/main\/java\/com\/kinsta\/stripejava\/backend<\/strong> y guarda en \u00e9l el siguiente c\u00f3digo:<\/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>El c\u00f3digo de este m\u00e9todo es bastante similar al del m\u00e9todo <code>hostedCheckout<\/code>, salvo que el modo establecido para crear la sesi\u00f3n es suscripci\u00f3n en lugar de producto y que, antes de crear la sesi\u00f3n, se establece un valor para el intervalo de recurrencia de la suscripci\u00f3n.<\/p>\n<p>Esto indica a Stripe que trate este pago como un pago de suscripci\u00f3n en lugar de un pago \u00fanico. De forma similar al m\u00e9todo <code>hostedCheckout<\/code>, este m\u00e9todo tambi\u00e9n devuelve la URL de la p\u00e1gina de pago alojada como respuesta HTTP al cliente. El cliente est\u00e1 configurado para redirigirse a la URL recibida, permitiendo al cliente completar el pago.<\/p>\n<p>Puedes reiniciar los servidores de desarrollo tanto del cliente como del servidor y ver la nueva p\u00e1gina de suscripci\u00f3n en acci\u00f3n. Este es su aspecto<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/hosted-subscription-checkout-flow.gif\" alt=\"Flujo de pago de una suscripci\u00f3n alojada.\" width=\"1914\" height=\"976\"><figcaption id=\"caption-attachment-163061\" class=\"wp-caption-text\">Flujo de pago de una suscripci\u00f3n alojada.<\/figcaption><\/figure>\n<h3>Cancelar una suscripci\u00f3n existente<\/h3>\n<p>Ahora que ya sabes c\u00f3mo crear nuevas suscripciones, vamos a aprender a permitir que tus clientes cancelen las suscripciones existentes. Dado que la aplicaci\u00f3n de demostraci\u00f3n creada en este tutorial no contiene ninguna configuraci\u00f3n de autenticaci\u00f3n, utiliza un formulario para permitir que el cliente introduzca su correo electr\u00f3nico para buscar sus suscripciones y, a continuaci\u00f3n, proporciona a cada elemento de suscripci\u00f3n un bot\u00f3n de cancelaci\u00f3n para que el usuario pueda cancelarla.<\/p>\n<p>Para ello, tendr\u00e1s que hacer lo siguiente:<\/p>\n<ol>\n<li>Actualizar el componente <code>CartItem<\/code> para que muestre un bot\u00f3n de cancelaci\u00f3n en la p\u00e1gina de cancelaci\u00f3n de suscripciones.<\/li>\n<li>Crea un componente <code>CancelSubscription<\/code> que muestre primero un campo de entrada y un bot\u00f3n para que el cliente busque suscripciones utilizando su direcci\u00f3n de correo electr\u00f3nico y, a continuaci\u00f3n, muestre una lista de suscripciones utilizando el componente <code>CartItem<\/code> actualizado.<\/li>\n<li>Crea un nuevo m\u00e9todo en el servidor backend que pueda buscar suscripciones desde el backend de Stripe utilizando la direcci\u00f3n de correo electr\u00f3nico del cliente.<\/li>\n<li>Crea un nuevo m\u00e9todo en el servidor backend que pueda cancelar una suscripci\u00f3n bas\u00e1ndose en el ID de suscripci\u00f3n que se le haya pasado.<\/li>\n<\/ol>\n<p>Empieza actualizando el componente <code>CartItem<\/code> para que tenga este aspecto:<\/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>A continuaci\u00f3n, crea un componente <strong>CancelSubscription.tsx<\/strong> en el directorio de <strong>routes<\/strong>\u00a0de tu fronted y guarda en \u00e9l el siguiente c\u00f3digo:<\/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>Este componente muestra un campo de entrada y un bot\u00f3n para que los clientes introduzcan su correo electr\u00f3nico y comiencen a buscar suscripciones. Si se encuentran suscripciones, el campo de entrada y el bot\u00f3n se ocultan, y se muestra una lista de suscripciones en la pantalla. Para cada elemento de suscripci\u00f3n, el componente pasa un m\u00e9todo <code>removeSubscription<\/code> que solicita al servidor Java backend que cancele la suscripci\u00f3n en el backend de Stripe.<\/p>\n<p>Para adjuntarlo a la ruta <code>\/cancel-subscription<\/code> en tu aplicaci\u00f3n frontend, a\u00f1ade el siguiente c\u00f3digo en el array pasado a la llamada <code>createBrowserRouter<\/code> en el componente <code>App<\/code>:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/cancel-subscription',\n            element: (\n                &lt;CancelSubscription\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Para buscar suscripciones en el servidor backend, a\u00f1ade un m\u00e9todo <code>viewSubscriptions<\/code> en la clase <code>PaymentController<\/code> de tu proyecto backend con el siguiente contenido:<\/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>El m\u00e9todo anterior encuentra primero el objeto cliente para el usuario dado en Stripe. A continuaci\u00f3n, busca las suscripciones activas del cliente. Una vez recibida la lista de suscripciones, extrae los art\u00edculos de las mismas y busca los productos correspondientes en la base de datos de productos de la app para enviarlos al frontend. Esto es importante porque el ID con el que el frontend identifica cada producto en la base de datos de la app puede o no ser el mismo que el ID del producto almacenado en Stripe.<\/p>\n<p>Por \u00faltimo, crea un <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>Este m\u00e9todo recupera el objeto de suscripci\u00f3n de Stripe, llama al m\u00e9todo cancelar en \u00e9l y, a continuaci\u00f3n, devuelve el estado de la suscripci\u00f3n al cliente. Sin embargo, para poder ejecutar esto, necesitas actualizar tu objeto DTO para a\u00f1adir el campo <code>subscriptionId<\/code>. Hazlo a\u00f1adiendo el siguiente campo y m\u00e9todo en la clase <code>RequestDTO<\/code>:<\/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>Una vez a\u00f1adido esto, ya puedes volver a ejecutar el servidor de desarrollo tanto para el backend como para la aplicaci\u00f3n del frontend y ver el flujo de cancelaci\u00f3n en acci\u00f3n:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/subscription-cancel-flow-1024x522.gif\" alt=\"Un flujo de cancelaci\u00f3n de suscripciones.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163062\" class=\"wp-caption-text\">Un flujo de cancelaci\u00f3n de suscripciones.<\/figcaption><\/figure>\n<h3>Configurar pruebas gratuitas para suscripciones con transacciones de valor cero<\/h3>\n<p>Una caracter\u00edstica com\u00fan a la mayor\u00eda de las suscripciones modernas es ofrecer un breve periodo de prueba gratuito antes de cobrar al usuario. Esto permite a los usuarios explorar el producto o servicio sin invertir en \u00e9l. Sin embargo, lo mejor es almacenar los datos de pago del cliente al inscribirlo para la prueba gratuita, de modo que puedas cobrarle f\u00e1cilmente en cuanto termine la prueba.<\/p>\n<p>Stripe simplifica enormemente la creaci\u00f3n de este tipo de suscripciones. Para empezar, genera un nuevo componente dentro del directorio <strong>frontend\/routes<\/strong> llamado <strong>SubscriptionWithTrial.tsx<\/strong>, y pega el siguiente c\u00f3digo:<\/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>Este componente reutiliza los componentes creados anteriormente. La diferencia clave entre \u00e9ste y el componente <code>NewSubscription<\/code> es que pasa el modo para <code>TotalFooter<\/code> como <strong>trial<\/strong> en lugar de <strong>subscription<\/strong>. Esto hace que el componente <code>TotalFooter<\/code> muestre un texto que dice que el cliente puede empezar la prueba gratuita ahora, pero que se le cobrar\u00e1 al cabo de un mes.<\/p>\n<p>Para adjuntar este componente a la ruta <code>\/subscription-with-trial<\/code> en tu aplicaci\u00f3n frontend, a\u00f1ade el siguiente c\u00f3digo en el array pasado a la llamada <code>createBrowserRouter<\/code> en el componente <code>App<\/code>:<\/p>\n<pre><code class=\"language-bash\">       {\n            path: '\/subscription-with-trial',\n            element: (\n                &lt;SubscriptionWithTrial\/&gt;\n            )\n        },<\/code><\/pre>\n<p>Para construir el flujo de pago para suscripciones con <strong>trial<\/strong> en el backend, crea un nuevo m\u00e9todo llamado <code>newSubscriptionWithTrial<\/code> en la clase <code>PaymentController<\/code> y a\u00f1ade el siguiente c\u00f3digo:<\/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>Este c\u00f3digo es bastante similar al del m\u00e9todo <code>newSubscription<\/code>. La \u00fanica diferencia (y la m\u00e1s importante) es que se pasa un periodo de prueba al objeto de par\u00e1metros de creaci\u00f3n de sesi\u00f3n con el valor <code>30<\/code>, que indica un periodo de prueba gratuito de 30 d\u00edas.<\/p>\n<p>Ahora puedes guardar los cambios y volver a ejecutar el servidor de desarrollo para el backend y el frontend para ver en acci\u00f3n el flujo de trabajo de suscripci\u00f3n con periodo de prueba gratuito:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/subscription-free-trial-flow-1024x522.gif\" alt=\"Un flujo de suscripci\u00f3n con prueba gratuita.\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163063\" class=\"wp-caption-text\">Un flujo de suscripci\u00f3n con prueba gratuita.<\/figcaption><\/figure>\n<h2>Crear Facturas para Pagos<\/h2>\n<p>Para las suscripciones, Stripe genera autom\u00e1ticamente facturas para cada pago, incluso si se trata de una transacci\u00f3n de valor cero para la inscripci\u00f3n a la prueba. Para pagos puntuales, puedes elegir crear facturas si es necesario.<\/p>\n<p>Para empezar a asociar todos los pagos con facturas, actualiza el cuerpo de la carga \u00fatil que se env\u00eda en la funci\u00f3n <code>initiatePayment<\/code> del componente <code>CustomerDetails<\/code> en la aplicaci\u00f3n del frontend para que contenga la siguiente propiedad:<\/p>\n<pre><code class=\"language-js\">invoiceNeeded: true<\/code><\/pre>\n<p>Tambi\u00e9n tendr\u00e1s que a\u00f1adir esta propiedad en el cuerpo de la carga \u00fatil que se env\u00eda al servidor en la funci\u00f3n <code>createTransactionSecret<\/code> del componente <code>IntegratedCheckout<\/code>.<\/p>\n<p>A continuaci\u00f3n, actualiza las rutas del backend para que comprueben esta nueva propiedad y actualiza las llamadas al SDK de Stripe en consecuencia.<\/p>\n<p>En el m\u00e9todo de pago alojado, para a\u00f1adir la funcionalidad de facturaci\u00f3n, actualiza el m\u00e9todo <code>hostedCheckout<\/code> a\u00f1adiendo las siguientes l\u00edneas de c\u00f3digo:<\/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>Esto comprobar\u00e1 el campo <code>invoiceNeeded<\/code> y establecer\u00e1 los par\u00e1metros de creaci\u00f3n en consecuencia.<\/p>\n<p>A\u00f1adir una factura a un pago integrado es un poco complicado. No puedes simplemente establecer un par\u00e1metro para indicar a Stripe que cree una factura con el pago autom\u00e1ticamente. Debes crear manualmente la factura y despu\u00e9s una intenci\u00f3n de pago vinculada.<\/p>\n<p>Si la intenci\u00f3n de pago se paga con \u00e9xito y se completa, la factura se marca como pagada; de lo contrario, la factura permanece sin pagar. Aunque esto tiene sentido l\u00f3gico, puede ser un poco complejo de implementar (especialmente cuando no hay ejemplos claros o referencias a seguir).<\/p>\n<p>Para implementarlo, actualiza el m\u00e9todo <code>integratedCheckout<\/code> para que tenga este aspecto:<\/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>El c\u00f3digo antiguo de este m\u00e9todo se traslada al bloque <code>if<\/code> que comprueba si el campo <code>invoiceNeeded<\/code> es <code>false<\/code>. Si se comprueba que es cierto, el m\u00e9todo crea ahora una factura con partidas de factura y la marca como finalizada para que se pueda pagar.<\/p>\n<p>A continuaci\u00f3n, recupera la intenci\u00f3n de pago creada autom\u00e1ticamente cuando se finaliz\u00f3 la factura y env\u00eda el secreto de cliente de esta intenci\u00f3n de pago al cliente. Una vez que el cliente completa el flujo de pago integrado, se cobra el pago y la factura se marca como pagada.<\/p>\n<p>Esto completa la configuraci\u00f3n necesaria para empezar a generar facturas desde tu aplicaci\u00f3n. Puedes dirigirte a <a href=\"https:\/\/dashboard.stripe.com\/test\/invoices\" target=\"_blank\" rel=\"noopener noreferrer\">la secci\u00f3n de facturas<\/a> de tu panel de control de Stripe para ver las facturas que genera tu aplicaci\u00f3n con cada compra y pago de suscripci\u00f3n.<\/p>\n<p>Sin embargo, Stripe tambi\u00e9n te permite acceder a las facturas a trav\u00e9s de su API para crear una experiencia de autoservicio para que los clientes puedan descargar las facturas cuando lo deseen.<\/p>\n<p>Para ello, crea un nuevo componente en el directorio <strong>frontend\/routes<\/strong> llamado <strong>ViewInvoices.tsx<\/strong>. Pega en \u00e9l el siguiente c\u00f3digo:<\/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>Similar al componente <code>CancelSubscription<\/code>, este componente muestra un campo de entrada para que el cliente introduzca su correo electr\u00f3nico y un bot\u00f3n para buscar facturas. Una vez encontradas las facturas, el campo de entrada y el bot\u00f3n se ocultan, y se muestra al cliente una lista de facturas con el n\u00famero de factura, el importe total y un bot\u00f3n para descargar el PDF de la factura.<\/p>\n<p>Para implementar el m\u00e9todo del backend que busca las facturas del cliente dado y devuelve la informaci\u00f3n relevante (n\u00famero de factura, importe y URL del PDF), a\u00f1ade el siguiente m\u00e9todo en tu clase <code>PaymentController<\/code> del backend;<\/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>El m\u00e9todo busca primero al cliente por la direcci\u00f3n de correo electr\u00f3nico que se le ha proporcionado. A continuaci\u00f3n, busca las facturas de este cliente que est\u00e9n marcadas como pagadas. Una vez encontrada la lista de facturas, extrae el n\u00famero de factura, el importe y la URL del PDF, y devuelve una lista con esta informaci\u00f3n a la aplicaci\u00f3n cliente.<\/p>\n<p>Este es el aspecto del flujo de facturas:<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/viewing-invoices-flow-1024x522.gif\" alt=\"Visualizaci\u00f3n de facturas\" width=\"1024\" height=\"522\"><figcaption id=\"caption-attachment-163064\" class=\"wp-caption-text\">Visualizaci\u00f3n de facturas<\/figcaption><\/figure>\n<p>Esto completa el desarrollo de nuestra aplicaci\u00f3n Java de demostraci\u00f3n (<a href=\"https:\/\/github.com\/krharsh17\/stripe-payments-java-react-frontend\" target=\"_blank\" rel=\"noopener noreferrer\">frontend<\/a> y <a href=\"https:\/\/github.com\/krharsh17\/stripe-payments-java-react-backend\" target=\"_blank\" rel=\"noopener noreferrer\">backend<\/a>). En la siguiente secci\u00f3n, aprender\u00e1s a desplegar esta aplicaci\u00f3n en Kinsta para que puedas acceder a ella online.<\/p>\n<h2>Desplegar Tu Aplicaci\u00f3n en Kinsta<\/h2>\n<p>Una vez que tu aplicaci\u00f3n est\u00e9 lista, puedes desplegarla en Kinsta. Kinsta soporta despliegues desde tu proveedor Git preferido (<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> o <a href=\"https:\/\/docs.sevalla.com\/applications\/git\/gitlab#authorize-the-kinsta-gitlab-application\">GitLab<\/a>). Conecta los repositorios de c\u00f3digo fuente de tu aplicaci\u00f3n a Kinsta, y autom\u00e1ticamente desplegar\u00e1 tu aplicaci\u00f3n cada vez que haya un cambio en el c\u00f3digo.<\/p>\n<h3>Prepara tus proyectos<\/h3>\n<p>Para desplegar tus aplicaciones en producci\u00f3n, identifica los comandos de compilaci\u00f3n y despliegue que utilizar\u00e1 Kinsta. Para el frontend, aseg\u00farate de que tu archivo <strong>package.json<\/strong> tiene definidos los siguientes scripts:<\/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>Tambi\u00e9n necesitar\u00e1s instalar el paquete <a href=\"https:\/\/www.npmjs.com\/package\/serve\" target=\"_blank\" rel=\"noopener noreferrer\">serve<\/a> npm que te permite servir sitios web est\u00e1ticos. Este paquete se utilizar\u00e1 para servir la compilaci\u00f3n de producci\u00f3n de tu aplicaci\u00f3n desde el entorno de despliegue de Kinsta. Puedes instalarlo ejecutando el siguiente comando:<\/p>\n<pre><code class=\"language-bash\">npm i serve<\/code><\/pre>\n<p>Una vez que construyas tu aplicaci\u00f3n utilizando vite, toda la aplicaci\u00f3n se empaquetar\u00e1 en un \u00fanico archivo, <strong>index.html<\/strong>, ya que la configuraci\u00f3n de React que est\u00e1s utilizando en este tutorial est\u00e1 pensada para crear aplicaciones de una sola p\u00e1gina. Aunque esto no suponga una gran diferencia para tus usuarios, deber\u00e1s establecer alguna configuraci\u00f3n adicional para gestionar el enrutamiento y la navegaci\u00f3n del navegador en este tipo de aplicaciones.<\/p>\n<p>Con la configuraci\u00f3n actual, s\u00f3lo puedes acceder a tu aplicaci\u00f3n en la URL base de tu despliegue. Si la URL base de la implantaci\u00f3n es <strong>example.com<\/strong>, cualquier petici\u00f3n a <strong>example<\/strong><strong>.com\/some-route<\/strong>\u00a0conducir\u00e1 a errores HTTP 404.<\/p>\n<p>Esto se debe a que tu servidor s\u00f3lo tiene un archivo que servir, el archivo <strong>index.html<\/strong>. Una solicitud enviada a <strong>example<\/strong>.<strong>com\/some-route<\/strong> empezar\u00e1 a buscar el archivo <strong>some-route<\/strong><strong>\/index.html<\/strong>, que no existe; por lo tanto, recibir\u00e1 una respuesta 404 No encontrado.<\/p>\n<p>Para solucionarlo, crea un archivo llamado <strong>serve.json<\/strong> en tu carpeta <strong>frontend\/public<\/strong> y guarda en \u00e9l el siguiente c\u00f3digo:<\/p>\n<pre><code class=\"language-js\">{\n  \"rewrites\": [\n    { \"source\": \"*\", \"destination\": \"\/index.html\" }\n  ]\n}\n<\/code><\/pre>\n<p>Este archivo indicar\u00e1 a <code>serve<\/code> que reescriba todas las solicitudes entrantes para dirigirlas al archivo <strong>index.html<\/strong>, sin dejar de mostrar en la respuesta la ruta a la que se envi\u00f3 la solicitud original. Esto te ayudar\u00e1 a servir correctamente las p\u00e1ginas de \u00e9xito y fracaso de tu aplicaci\u00f3n cuando Stripe redirija a tus clientes de vuelta a tu aplicaci\u00f3n.<\/p>\n<p>Para el backend, crea un <a href=\"https:\/\/kinsta.com\/es\/blog\/dockerfile-entrypoint\/\">Dockerfile<\/a> para configurar el entorno adecuado para tu aplicaci\u00f3n Java. El uso de un Dockerfile garantiza que el entorno proporcionado a tu aplicaci\u00f3n Java es el mismo en todos los hosts (ya sea tu host de desarrollo local o el host de despliegue de Kinsta) y puedes asegurarte de que tu aplicaci\u00f3n se ejecuta como se espera.<\/p>\n<p>Para ello, crea un archivo llamado <strong>Dockerfile<\/strong> en la carpeta <strong>backend<\/strong> y guarda en \u00e9l el siguiente contenido:<\/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>Este archivo indica al tiempo de ejecuci\u00f3n que utilice la imagen Java <a href=\"https:\/\/openjdk.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenJDK<\/a> como base para el contenedor de despliegue, ejecute el comando <code>.\/mvnw clean package<\/code> para crear el archivo <strong>JAR<\/strong> de tu aplicaci\u00f3n y utilice el comando <code>java -jar &lt;jar-file&gt;<\/code> para ejecutarlo. Esto completa la preparaci\u00f3n del c\u00f3digo fuente para su despliegue en Kinsta.<\/p>\n<h3>Configurar los repositorios de GitHub<\/h3>\n<p>Para empezar a desplegar las aplicaciones, crea dos repositorios de GitHub para alojar el c\u00f3digo fuente de tus aplicaciones. Si utilizas la CLI de GitHub, puedes hacerlo a trav\u00e9s del terminal ejecutando los siguientes comandos:<\/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>Esto deber\u00eda crear nuevos repositorios de GitHub en tu cuenta y enviar el c\u00f3digo de tus aplicaciones a ellos. Deber\u00edas poder acceder a los repositorios frontend y backend. A continuaci\u00f3n, despliega estos repositorios en Kinsta siguiendo estos pasos:<\/p>\n<ol>\n<li>Inicia sesi\u00f3n o crea tu cuenta Kinsta en el panel <a href=\"https:\/\/my.kinsta.com\/?lang=es\">MyKinsta<\/a>.<\/li>\n<li>En la barra lateral izquierda, haz clic en <strong>Aplicaciones<\/strong> y luego en <strong>A\u00f1adir aplicaci\u00f3n<\/strong>.<\/li>\n<li>En el modal que aparece, elige el repositorio que quieres desplegar. Si tienes varias ramas, puedes seleccionar la rama deseada y dar un nombre a tu aplicaci\u00f3n.<\/li>\n<li>Selecciona una de las ubicaciones de centros de datos disponibles en la lista de opciones 24. Kinsta detecta autom\u00e1ticamente el comando de inicio de tu aplicaci\u00f3n.<\/li>\n<\/ol>\n<p>Recuerda que debes proporcionar a tus aplicaciones frontend y backend algunas <a href=\"https:\/\/kinsta.com\/es\/blog\/variables-de-entorno\/\">variables de entorno<\/a> para que funcionen correctamente. La aplicaci\u00f3n del frontend necesita las siguientes variables de entorno:<\/p>\n<ul>\n<li>VITE_STRIPE_API_KEY<\/li>\n<li>VITE_SERVIDOR_BASE_URL<\/li>\n<li>VITE_CLIENT_BASE_URL<\/li>\n<\/ul>\n<p>Para desplegar la aplicaci\u00f3n backend, haz exactamente lo mismo que hicimos para el frontend, pero para el paso <strong>Entorno de compilaci\u00f3n<\/strong>, selecciona el bot\u00f3n de opci\u00f3n <strong>Utilizar Dockerfile para configurar la imagen del contenedor<\/strong> e introduce <code>Dockerfile<\/code> como ruta Dockerfile para tu aplicaci\u00f3n backend.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/application-form-build-environment-details-1024x523.png\" alt=\"Configuraci\u00f3n de los detalles del entorno de compilaci\u00f3n\" width=\"1024\" height=\"523\"><figcaption id=\"caption-attachment-163065\" class=\"wp-caption-text\">Configuraci\u00f3n de los detalles del entorno de compilaci\u00f3n<\/figcaption><\/figure>\n<p>Recuerda a\u00f1adir las variables de entorno del backend:<\/p>\n<ul>\n<li>CLIENT_BASE_URL<\/li>\n<li>STRIPE_API_KEY<\/li>\n<\/ul>\n<p>Una vez completado el despliegue, dir\u00edgete a la p\u00e1gina de detalles de tus aplicaciones y accede desde all\u00ed a la URL del despliegue.<\/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:\/\/kinsta.com\/wp-content\/uploads\/2023\/09\/hosted-url-for-kinsta-deployed-apps-1024x523.png\" alt=\"La URL alojada de las aplicaciones desplegadas en Kinsta\" width=\"1024\" height=\"523\"><figcaption id=\"caption-attachment-163066\" class=\"wp-caption-text\">La URL alojada de las aplicaciones desplegadas en Kinsta<\/figcaption><\/figure>\n<p>Extrae las URLs de las dos aplicaciones desplegadas. Dir\u00edgete al <a href=\"https:\/\/dashboard.stripe.com\/test\/apikeys\" target=\"_blank\" rel=\"noopener noreferrer\">panel de Stripe<\/a> para obtener tus claves API secretas y publicables.<\/p>\n<p>Aseg\u00farate de proporcionar la clave publicable de Stripe a tu aplicaci\u00f3n frontend (no la clave secreta). Aseg\u00farate tambi\u00e9n de que tus URLs base no tienen una barra inclinada (<code>\/<\/code>) al final. Las rutas ya tienen barras inclinadas al principio, por lo que tener una barra inclinada al final de las URL base har\u00e1 que se a\u00f1adan dos barras inclinadas a las URL finales.<\/p>\n<p>Para tu aplicaci\u00f3n backend, a\u00f1ade la clave secreta del panel de control de Stripe (no la clave publicable). Adem\u00e1s, aseg\u00farate de que la URL de tu cliente no tiene una barra oblicua (<code>\/<\/code>) al final.<\/p>\n<p>Una vez a\u00f1adidas las variables, ve a la pesta\u00f1a <strong>Despliegues <\/strong>de la aplicaci\u00f3n y haz clic en el bot\u00f3n de <strong>redespliegue<\/strong> de tu aplicaci\u00f3n backend. Esto completa la configuraci\u00f3n \u00fanica que necesitas para proporcionar a tus despliegues Kinsta las credenciales a trav\u00e9s de las variables de entorno.<\/p>\n<p>A continuaci\u00f3n, puedes confirmar los cambios en tu control de versiones. Kinsta redistribuir\u00e1 autom\u00e1ticamente tu aplicaci\u00f3n si marcaste la opci\u00f3n durante la distribuci\u00f3n; de lo contrario, tendr\u00e1s que activar la redistribuci\u00f3n manualmente.<\/p>\n<h2>Resumen<\/h2>\n<p>En este art\u00edculo, has aprendido c\u00f3mo funciona Stripe y los flujos de pago que ofrece. Tambi\u00e9n has aprendido, a trav\u00e9s de un ejemplo detallado, c\u00f3mo integrar Stripe en tu aplicaci\u00f3n Java para aceptar pagos \u00fanicos, establecer suscripciones, ofrecer pruebas gratuitas y generar facturas de pago.<\/p>\n<p>Utilizando Stripe y Java juntos, puedes ofrecer una soluci\u00f3n de pago robusta a tus clientes que puede escalar bien e integrarse perfectamente con tu ecosistema existente de aplicaciones y herramientas.<\/p>\n<p><em>\u00bfUtilizas Stripe en tu aplicaci\u00f3n para cobrar pagos? En caso afirmativo, \u00bfcu\u00e1l de los dos flujos prefieres: alojado, personalizado o in-app? H\u00e1znoslo saber en los comentarios m\u00e1s abajo.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A medida que aumentan las transacciones digitales, la capacidad de integrar perfectamente las pasarelas de pago se ha convertido en una habilidad cr\u00edtica para los desarrolladores. &#8230;<\/p>\n","protected":false},"author":199,"featured_media":70438,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kinsta_gated_content":false,"_kinsta_gated_content_redirect":"","footnotes":""},"tags":[],"topic":[1270,1336,1321],"class_list":["post-70437","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","topic-api","topic-lenguajes-desarrollo-web","topic-react"],"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>Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot - Kinsta\u00ae<\/title>\n<meta name=\"description\" content=\"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot\" \/>\n<meta property=\"og:description\" content=\"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/\" \/>\n<meta property=\"og:site_name\" content=\"Kinsta\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/kinsta.es\/\" \/>\n<meta property=\"article:published_time\" content=\"2023-10-02T14:31:49+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-10-12T15:59:28+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/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=\"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@Kinsta_ES\" \/>\n<meta name=\"twitter:site\" content=\"@Kinsta_ES\" \/>\n<meta name=\"twitter:label1\" content=\"Escrito por\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jeremy Holcombe\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data2\" content=\"51 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/\"},\"author\":{\"name\":\"Jeremy Holcombe\",\"@id\":\"https:\/\/kinsta.com\/es\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21\"},\"headline\":\"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot\",\"datePublished\":\"2023-10-02T14:31:49+00:00\",\"dateModified\":\"2023-10-12T15:59:28+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/\"},\"wordCount\":8282,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/es\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg\",\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/\",\"url\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/\",\"name\":\"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot - Kinsta\u00ae\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/es\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg\",\"datePublished\":\"2023-10-02T14:31:49+00:00\",\"dateModified\":\"2023-10-12T15:59:28+00:00\",\"description\":\"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage\",\"url\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg\",\"contentUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg\",\"width\":1460,\"height\":730},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/kinsta.com\/es\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"API\",\"item\":\"https:\/\/kinsta.com\/es\/secciones\/api\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/kinsta.com\/es\/#website\",\"url\":\"https:\/\/kinsta.com\/es\/\",\"name\":\"Kinsta\u00ae\",\"description\":\"Soluciones de alojamiento premium, r\u00e1pidas y seguras\",\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/es\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/kinsta.com\/es\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/kinsta.com\/es\/#organization\",\"name\":\"Kinsta\",\"url\":\"https:\/\/kinsta.com\/es\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg\",\"contentUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg\",\"width\":500,\"height\":500,\"caption\":\"Kinsta\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/kinsta.es\/\",\"https:\/\/x.com\/Kinsta_ES\",\"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:\/\/kinsta.com\/es\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21\",\"name\":\"Jeremy Holcombe\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/kinsta.com\/es\/#\/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:\/\/kinsta.com\/es\/blog\/author\/jeremyholcombe\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot - Kinsta\u00ae","description":"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.","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:\/\/kinsta.com\/es\/blog\/stripe-java-api\/","og_locale":"es_ES","og_type":"article","og_title":"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot","og_description":"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.","og_url":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/kinsta.es\/","article_published_time":"2023-10-02T14:31:49+00:00","article_modified_time":"2023-10-12T15:59:28+00:00","og_image":[{"width":1460,"height":730,"url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg","type":"image\/jpeg"}],"author":"Jeremy Holcombe","twitter_card":"summary_large_image","twitter_description":"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.","twitter_image":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg","twitter_creator":"@Kinsta_ES","twitter_site":"@Kinsta_ES","twitter_misc":{"Escrito por":"Jeremy Holcombe","Tiempo de lectura":"51 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#article","isPartOf":{"@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/"},"author":{"name":"Jeremy Holcombe","@id":"https:\/\/kinsta.com\/es\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21"},"headline":"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot","datePublished":"2023-10-02T14:31:49+00:00","dateModified":"2023-10-12T15:59:28+00:00","mainEntityOfPage":{"@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/"},"wordCount":8282,"commentCount":0,"publisher":{"@id":"https:\/\/kinsta.com\/es\/#organization"},"image":{"@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg","inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/","url":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/","name":"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot - Kinsta\u00ae","isPartOf":{"@id":"https:\/\/kinsta.com\/es\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage"},"image":{"@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg","datePublished":"2023-10-02T14:31:49+00:00","dateModified":"2023-10-12T15:59:28+00:00","description":"Aprende c\u00f3mo integrar a la perfecci\u00f3n Stripe en tu aplicaci\u00f3n Spring Boot para un procesamiento de pagos eficiente y de f\u00e1cil uso.","breadcrumb":{"@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#primaryimage","url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg","contentUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/09\/stripe-java-api.jpg","width":1460,"height":730},{"@type":"BreadcrumbList","@id":"https:\/\/kinsta.com\/es\/blog\/stripe-java-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/kinsta.com\/es\/"},{"@type":"ListItem","position":2,"name":"API","item":"https:\/\/kinsta.com\/es\/secciones\/api\/"},{"@type":"ListItem","position":3,"name":"Una Gu\u00eda Para la Integraci\u00f3n de Stripe en Spring Boot"}]},{"@type":"WebSite","@id":"https:\/\/kinsta.com\/es\/#website","url":"https:\/\/kinsta.com\/es\/","name":"Kinsta\u00ae","description":"Soluciones de alojamiento premium, r\u00e1pidas y seguras","publisher":{"@id":"https:\/\/kinsta.com\/es\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/kinsta.com\/es\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"https:\/\/kinsta.com\/es\/#organization","name":"Kinsta","url":"https:\/\/kinsta.com\/es\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/","url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg","contentUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg","width":500,"height":500,"caption":"Kinsta"},"image":{"@id":"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/kinsta.es\/","https:\/\/x.com\/Kinsta_ES","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:\/\/kinsta.com\/es\/#\/schema\/person\/4eee42881d7b5a73ebb4f58dd5223b21","name":"Jeremy Holcombe","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/kinsta.com\/es\/#\/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:\/\/kinsta.com\/es\/blog\/author\/jeremyholcombe\/"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/70437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/users\/199"}],"replies":[{"embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/comments?post=70437"}],"version-history":[{"count":15,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/70437\/revisions"}],"predecessor-version":[{"id":70873,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/70437\/revisions\/70873"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/70437\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/70437\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/70437\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/70437\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/70437\/translations\/de"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/70437\/translations\/es"},{"href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/70437\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/media\/70438"}],"wp:attachment":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/media?parent=70437"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/tags?post=70437"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/topic?post=70437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}