Home » Insights » Criando um fluxo de pagamentos com Stripe

Criando um fluxo de pagamentos com Stripe

Índice

    Criando um fluxo básico de pagamentos com cartão de credito.

    _______________________________________________________________________________________________________

    Introdução

    O Stripe é uma empresa de pagamentos muito queridinha entre os desenvolvedores, devido à alta qualidade de sua documentação e da quantidade de recursos que a plataforma oferece ao público dev. Nesse artigo, vamos entender um pouco sobre o fluxo básico de pagamentos no Stripe e como implementar o pagamento por cartão de crédito com uma certa customização para cada caso de uso.

    Entendendo o fluxo do Stripe

    O importante é nos atentarmos, e dividir esse fluxo em duas etapas: a criação da “intenção de pagamento” e o pagamento em si.

    Por mais complexo que possa parecer a primeira vista, o processo é relativamente simples e consiste em você realizar uma chamada na api do stripe, indicando uma “intenção de pagamento”, então, você recebe um client_secret e usa esse mesmo secret para realizar uma nova chamada e realizar o pagamento.

    A criação de uma intenção de pagamento tem o intuito de criar métrica na sua página de pagamentos, e verificar quantos clientes estão chegando até ela, com quais produtos, e se estão desistindo ou não da compra.

    Na prática

    Vamos prosseguir da seguinte forma, faremos uma chamada no nosso backend e ele ficará responsável por fazer a primeira chamada no stripe e nos retornar o client_secret.

    Depois, pelo próprio frontend, iremos realizar a chamada do pagamento no stripe e finalizar a compra.

    Para esse exemplo, vamos usar o Node.js no backend e o React.js no frontend, usando um componente fornecido pelo próprio stripe para o input e validação do cartão de crédito.

    Backend

    Nesse exemplo, o nosso backend será responsável apenas por executar a chamada de intenção de pagamento do strapi e não fará nenhuma operação no banco de dados, ou seja, vamos apenas criar a estrutura de um controller e definir uma rota, que no nosso exemplo será “/orders/create-payment-intent”.

    Você também pode fazer essa chamada no seu frontend, mas precisaria se atentar a alguns detalhes, por exemplo: o nosso backend possui os preços dos produtos de modo fidedigno, enquanto se eu realizar determinada chamada no meu frontend, usuários mal-intencionados podem de alguma forma alterar os preços do produto, gerando um compra com o valor alterado. Se mantermos a responsabilidade de criar a intenção de pagamento no backend, só enviamos os ids dos produtos e quantidade, se também for necessário, e o backend fica responsável por consultar os preços e outras informações do usuário, gerando o “orçamento” da nossa compra.

    Para começar a criar o nosso controller, o primeiro passo será instalar a biblioteca do stripe:

    yarn add stripe
    

    Vamos fazer a nossa chamada na rota “/orders/create-payment-intent”, passando um array de ids dos nossos produtos no body da requisição.

    Assim, iremos criar o controller da seguinte forma:

    // importação da biblioteca do stripe
    const stripe = require('stripe')(process.env.STRIPE_KEY);
    
    // implementação de acordo com o seu backend
    
    // método do seu controller
    createPaymentIntent: async (ctx) => {
        const { cart } = ctx.request.body
    
    		// cria um array que irá armazenar os produtos
        let products = []
    
        await Promise.all(
          cart?.map(async (product) => {
            const validatedProduct = // método para pegar as informações do produto
    
            if(validatedProduct) {
              products.push(validatedProduct);
            }
          })
        );
    
    		// verifica se o carrinho não está vazio
        if(!products.length) {
          ctx.response.status = 404;
          return {
            error: "No valid products found!"
          }
        }
    
    		// método para calcula o valor total do pedido
        const total = products.reduce((acc, product) => {
          return acc + product.price;
        }, 0)
    
    		// faz a chamada no stripe para criar a intenção de pagamento
        try {
          const paymentIntent = await stripe.paymentIntents.create({
            amount: total * 100,
            currency: 'usd',
            automatic_payment_methods: {enabled: true},
          });
    			
    			// retorna à intenção de pagamento
          return paymentIntent
        } catch (err) {
          return {
            error: err.raw.message,
          }
        }
      }
    

    Frontend

    No front, vamos utilizar um componente que o próprio stripe nos fornece, o CardElement. Para isso, vamos instalar duas bibliotecas do stripe:

    yarn add @stripe/react-stripe-js @stripe/stripe-js
    

    Agora vamos criar a nossa estrutura inicial, chamando o CardElement e criando uma função handleChange para verificar se o nosso input do cartão está vazio ou com algum erro:

    import { useState } from 'react'
    import { CardElement } from '@stripe/react-stripe-js'
    import { StripeCardElementChangeEvent } from '@stripe/stripe-js'
    
    const PaymentForm = () => {
    	// estados para controlar os erros e habilitar o botão de compra
    	const [error, setError] = useState<string | null>(null)
      const [disabled, setDisabled] = useState(true)
    
      const handleChange = async (event: StripeCardElementChangeEvent) => {
        setDisabled(event.empty)
        setError(event.error ? event.error.message : '')
      }
    
      return (
      <div>
          <CardElement
            options={{
    					// tira a necessidade de CEP para processar o pagamento
              hidePostalCode: true,
            }}
            onChange={handleChange}
          />
    
    			{// botão para submeter a compra}
          <button
            disabled={disabled || !!error}
          >
    				{// mostra o erro no preenchimento ou na compra}
    				{error && (
                <span>{error}</span>
            )}
            <span>Buy now</span>
          </button>
        </div>
      )
    }
    
    export default PaymentForm
    

    Agora vamos criar a nossa intenção de pagamento. Para isso, utilizaremos um useEffect, para que, ao carregar a página, já seja gerado a nossa intenção:

    import { useState, useEffect } from 'react'
    import { CardElement } from '@stripe/react-stripe-js'
    import { StripeCardElementChangeEvent } from '@stripe/stripe-js'
    import { createPaymentIntent } from 'stripe/methods'
    
    const PaymentForm = () => {
    	// hook que pega os items do carrinho de compras
    	const { items } = useCart()
    	// estado que recebe o client_secret
    	const [clientSecret, setClientSecret] = useState('')
    	const [error, setError] = useState<string | null>(null)
      const [disabled, setDisabled] = useState(true)
    
      const handleChange = async (event: StripeCardElementChangeEvent) => {
        setDisabled(event.empty)
        setError(event.error ? event.error.message : '')
      }
    
    	// criação da intenção de pagamento
    	useEffect(() => {
        async function setPaymentMode() {
          if (items.length) {
            // chamada na API /orders/create-payment-intent
            const data = await createPaymentIntent({
              items
            })
    
            // se der algum erro, setar o erro
            if (data.error) {
              setError(data.error)
              return
            }
    
            // se o payment intent foi válido, set client secret
            setFreeGames(false)
            setClientSecret(data.client_secret)
          }
        }
    
        setPaymentMode()
      }, [items])
    
      return (
      <div>
          <CardElement
            options={{
              hidePostalCode: true,
            }}
            onChange={handleChange}
          />
    
          <button
            disabled={disabled || !!error}
          >
    				{error && (
                <span>{error}</span>
            )}
            <span>Buy now</span>
          </button>
        </div>
      )
    }
    
    export default PaymentForm
    
    // stripe/methods.tsx
    import { CartItem } from 'hooks/use-cart'
    
    type PaymentIntentParams = {
      items: CartItem[]
    }
    
    export const createPaymentIntent = async ({
      items
    }: PaymentIntentParams) => {
      const response = await fetch(
        `${process.env.NEXT_PUBLIC_API_URL}/orders/create-payment-intent`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            cart: items
          })
        }
      )
    
      return await response.json()
    }
    

    Por fim, vamos criar a função de handleSubmit, para realizar a compra efetivamente:

    import { useState, useEffect } from 'react'
    import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js'
    import { StripeCardElementChangeEvent } from '@stripe/stripe-js'
    import { createPaymentIntent } from 'stripe/methods'
    
    const PaymentForm = () => {
    	const { items } = useCart()
    	// inicializar o hooks do stripe
    	const stripe = useStripe()
      const elements = useElements()
    
    	// estado de loading
    	const [loading, setLoading] = useState(false)
    	const [clientSecret, setClientSecret] = useState('')
    	const [error, setError] = useState<string | null>(null)
      const [disabled, setDisabled] = useState(true)
    
      const handleChange = async (event: StripeCardElementChangeEvent) => {
        setDisabled(event.empty)
        setError(event.error ? event.error.message : '')
      }
    
    	// criação da intenção de pagamento
    	useEffect(() => {
        async function setPaymentMode() {
          if (items.length) {
            // chamada na API /orders/create-payment-intent
            const data = await createPaymentIntent({
              items
            })
    
            // se der algum erro, setar o erro
            if (data.error) {
              setError(data.error)
              return
            }
    
            // se o paymetintent foi válido, set client secret
            setFreeGames(false)
            setClientSecret(data.client_secret)
          }
        }
    
        setPaymentMode()
      }, [items])
    
    	// função de submit
    	const handleSubmit = async (event: React.FormEvent) => {
        event.preventDefault()
    	
    		// verifica se os hooks do stripe já foram carregados
        if (!stripe || !elements) {
          return
        }
    
        setLoading(true)
    
        const payload = await stripe.confirmCardPayment(clientSecret, {
          payment_method: {
            card: elements.getElement(CardElement)!
          }
        })
    
        if (payload.error) {
          setError(`Payment failed: ${payload.error.message}`)
          setLoading(false)
        } else {
          setError(null)
          setLoading(false)
    
          // Aqui você pode salvar a compra no seu banco de dados
          // E depois redirecionar para uma página de sucesso
        }
      }
    
      return (
      <div>
    		<form onSubmit={handleSubmit}>
          <CardElement
            options={{
              hidePostalCode: true,
            }}
            onChange={handleChange}
          />
    
          <button
            disabled={disabled || !!error}
          >
    				{error && (
                <span>{error}</span>
            )}
            {!loading && <span>Buy now</span>}
          </button>
    		</form>
      </div>
      )
    }
    
    export default PaymentForm
    

    Então criamos um handleChange para lidar com o preenchimento do nosso CardElement, um useEffect para criar a intenção de pagamento, e por fim um handleSubmit para gerar a nossa compra. Com apenas três lógicas você já consegue implementar um fluxo de pagamento no seu app!

    Conclusão

    Dessa forma concluímos o nosso projeto. Como você pode ver, criar um fluxo de pagamentos não é nenhum bicho de sete cabeças e o CardElement do stripe faz toda a parte de validação para nós, o que facilita bastante o nosso desenvolvimento, além de ser um componente muito bem feito. O restante nós podemos customizar de acordo com a nossa aplicação e nossas regras de negócio.

     


    Publicado

    em

    por