Criando um fluxo de pagamentos com Stripe

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

Ao acessarmos a documentação do stripe, vamos nos deparar com o seguinte fluxo de pagamento:

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.

Docs: Accept a payment | Stripe Documentation

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 strapi:

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 logicas 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 falicita 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.

Arthur
Último artigo
Débito técnico: evite-o de uma vez por todas
Próximo artigo
Certificações em TI que todo programador deve ter em 2022