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.