Checkout GraphQL no Magento 2: das mutations do carrinho ao pedido
Toda loja Magento headless e todo app mobile construído sobre o Magento passa pela mesma sequência de mutations GraphQL para transformar um carrinho em pedido. A sequência não é longa, mas a ordem das chamadas, os IDs que cada uma precisa e a forma como os erros voltam são fáceis de errar. Este artigo percorre o fluxo completo de visitante e de cliente no Magento 2.4.8, com as queries que uso como ponto de partida e os erros que mais vejo.
O fluxo de relance
- Obter um cart ID: criar um carrinho de visitante ou ler o carrinho do cliente.
- Adicionar produtos.
- Definir o e-mail do visitante (só para visitantes).
- Definir o endereço de entrega e ler os métodos de frete disponíveis.
- Definir o endereço de cobrança.
- Definir o método de frete.
- Ler os meios de pagamento disponíveis e definir um.
- Finalizar o pedido.
Cada etapa depois da primeira recebe o cart ID. Para um cliente logado, toda requisição também leva o token do cliente em um header Authorization: Bearer. Como obter esse token está em autenticação de cliente no GraphQL.
Etapa 1: obter um carrinho
Para visitantes, o Magento 2.4.7 introduziu o createGuestCart, que devolve um objeto de carrinho. O antigo createEmptyCart ainda existe, mas está deprecado em favor dele.
mutation {
createGuestCart {
cart { id }
}
}
Para um cliente logado, não crie um carrinho. Leia o ativo, que o Magento cria se for preciso.
query {
customerCart { id total_quantity }
}
Guarde o cart ID no cliente. Se um visitante faz login com itens em um carrinho de visitante, chame o mergeCarts com o carrinho do visitante como origem e o carrinho do cliente como destino, e use o ID do carrinho do cliente daí em diante.
mutation Merge($guest: String!, $customer: String!) {
mergeCarts(source_cart_id: $guest, destination_cart_id: $customer) {
id
total_quantity
}
}
Etapa 2: adicionar produtos
O addProductsToCart trata todos os tipos de produto com um único formato de input. Um produto simples precisa só do SKU e da quantidade. Produtos configuráveis usam o SKU do pai mais os valores de uid das opções selecionadas, que você obtém de configurable_options na query de produto.
mutation Add($cartId: String!) {
addProductsToCart(
cartId: $cartId
cartItems: [
{ sku: "24-MB01", quantity: 1 }
{ sku: "MH01", quantity: 1, selected_options: ["Y29uZmlndXJhYmxlLzkzLzUz", "Y29uZmlndXJhYmxlLzE0NC8xNzE="] }
]
) {
cart {
total_quantity
itemsV2 { items { uid quantity product { sku name } } }
}
user_errors { code message }
}
}
Os valores de uid das opções acima são só exemplos. Leia os reais do seu catálogo.
Repare no user_errors. Essa mutation não lança um erro GraphQL quando um produto não pode ser adicionado (sem estoque, opção inválida, quantidade não permitida). Ela adiciona o que consegue e reporta o resto em user_errors. Se o seu frontend só confere o array errors do nível superior, os clientes vão achar que um item foi adicionado quando não foi.
Para mudar quantidades use o updateCartItems com o uid do item. Para remover um item use o removeItemFromCart. Cupons passam por applyCouponToCart e removeCouponFromCart.
Etapa 3: e-mail do visitante
Visitantes precisam ter um e-mail no carrinho antes de o pedido poder ser feito. Defina cedo, de preferência assim que o cliente digitar. Isso também dá um endereço para os fluxos de carrinho abandonado.
mutation Email($cartId: String!, $email: String!) {
setGuestEmailOnCart(input: { cart_id: $cartId, email: $email }) {
cart { email }
}
}
Etapa 4: endereço de entrega e métodos
mutation Ship($cartId: String!) {
setShippingAddressesOnCart(
input: {
cart_id: $cartId
shipping_addresses: [
{
address: {
firstname: "Ana"
lastname: "Rossi"
street: ["Via Roma 1"]
city: "Milano"
region: "MI"
postcode: "20121"
country_code: "IT"
telephone: "0200000000"
save_in_address_book: false
}
}
]
}
) {
cart {
shipping_addresses {
available_shipping_methods {
carrier_code
method_code
carrier_title
method_title
amount { value currency }
available
error_message
}
}
}
}
}
Para um cliente logado você pode passar customer_address_id em vez de um endereço completo. O tratamento de região depende do país. Países com lista de regiões no Magento aceitam um region_id ou um código de região, os outros aceitam texto livre. As regras de validação vêm da configuração da loja, então um CEP ou telefone que é obrigatório no admin também é obrigatório aqui.
A resposta lista os métodos de frete disponíveis para aquele endereço. Mostre só os que têm available: true e exiba o error_message quando uma transportadora devolver um, porque transportadoras que falham em silêncio são uma causa comum de tickets de suporte do tipo "nenhum método de frete".
Etapa 5: endereço de cobrança
A maioria dos checkouts assume por padrão cobrança igual à entrega. Use same_as_shipping em vez de enviar o endereço duas vezes.
mutation Bill($cartId: String!) {
setBillingAddressOnCart(
input: { cart_id: $cartId, billing_address: { same_as_shipping: true } }
) {
cart { billing_address { firstname lastname city } }
}
}
Etapa 6: método de frete
mutation Method($cartId: String!) {
setShippingMethodsOnCart(
input: {
cart_id: $cartId
shipping_methods: [{ carrier_code: "flatrate", method_code: "flatrate" }]
}
) {
cart {
prices {
grand_total { value currency }
}
available_payment_methods { code title }
}
}
}
Os totais agora incluem o frete. Pedir available_payment_methods na mesma resposta economiza uma ida e volta, porque a disponibilidade de pagamento pode depender do endereço, do método de frete e do total.
Etapa 7: meio de pagamento
mutation Pay($cartId: String!) {
setPaymentMethodOnCart(
input: { cart_id: $cartId, payment_method: { code: "checkmo" } }
) {
cart { selected_payment_method { code title } }
}
}
Métodos offline precisam só do código. Métodos online normalmente precisam de dados extras no input payment_method, como um token criado pelo SDK de frontend do provedor. Cada módulo de pagamento define os próprios campos de input no seu schema.graphqls. Leia esse arquivo, não a documentação REST do módulo, para ver o que o GraphQL espera. Se um módulo de pagamento não tem suporte a GraphQL, você não consegue usá-lo em um checkout headless sem escrever esse suporte. Um método customizado precisa dele desde o início, como visto em criando um meio de pagamento customizado.
Etapa 8: finalizar o pedido
mutation Place($cartId: String!) {
placeOrder(input: { cart_id: $cartId }) {
orderV2 { number }
errors { code message }
}
}
No 2.4.7 e posteriores, o placeOrder devolve uma lista errors com códigos de erro para falhas conhecidas, e o orderV2 substituiu o campo antigo order. Trate tanto os erros estruturados quanto os erros GraphQL do nível superior. Depois de um pedido bem-sucedido, o carrinho não está mais ativo. Limpe o cart ID guardado no cliente e, para clientes, leia o customerCart de novo para obter um novo.
Erros comuns
Reusar um carrinho depois do pedido
O frontend mantém o cart ID antigo e o próximo add to cart falha com um erro de carrinho não encontrado ou não ativo. Sempre resete o ID depois que o placeOrder der certo e trate o erro criando um carrinho novo se ele acontecer mesmo assim.
Misturar carrinhos de visitante e de cliente
Enviar um token de cliente com o ID de um carrinho de visitante, ou chamar mutations de visitante para um carrinho de cliente, falha com erros de autorização. Controle qual tipo de carrinho você tem em mãos e faça o merge no login.
Confiar nos totais no cliente
Nunca calcule totais no frontend. Leia prices do carrinho depois de cada mudança. Regras de preço do carrinho, impostos e frete vivem todos no servidor. Se você adiciona uma taxa própria, implemente como um totals collector e exponha no GraphQL, como descrito em totals collector para taxa customizada.
Fazer cache de queries do carrinho
Queries e mutations do carrinho nunca devem ser cacheadas pelo frontend ou pela CDN. Envie mutations como POST e marque as leituras do carrinho como no-store no seu cliente.
Adicionando dados customizados ao carrinho
Campos customizados de checkout precisam de um lugar no GraphQL. O padrão usual é uma mutation customizada que grava o valor na quote, mais um campo no tipo Cart que o lê de volta. Declare os dois no schema.graphqls do seu módulo e implemente resolvers que carregam o carrinho pelo Magento\QuoteGraphQl\Model\Cart\GetCartForUser, que verifica se o carrinho pertence ao usuário atual. Copie o valor da quote para o pedido na finalização, como faria com qualquer campo de checkout.
FAQ
Devo usar createEmptyCart ou createGuestCart?
No 2.4.7 e posteriores use createGuestCart. O createEmptyCart ainda funciona, mas está deprecado.
Por que o addProductsToCart dá certo mas o item não aparece?
Problemas no nível do item são devolvidos em user_errors, não no array errors do nível superior. Confira user_errors depois de todo add.
Como pago com cartão pelo GraphQL?
O SDK de frontend do provedor de pagamento cria um token, e você o passa no input payment_method do setPaymentMethodOnCart, usando os campos que o módulo de pagamento define no seu schema.graphqls.
O que acontece com o carrinho depois do placeOrder?
Ele fica inativo. Limpe o cart ID no cliente e, para clientes logados, leia o customerCart de novo para obter um novo carrinho ativo.
Atendo como freelancer em Magento e Adobe Commerce, incluindo checkouts headless. Se precisar de ajuda com isso, fale comigo.
Artigos relacionados
Login de cliente, tokens e segurança no GraphQL do Magento 2
Autenticação de cliente com o GraphQL do Magento 2: tokens de login, lifetime, logout, cadastro, reset de senha, armazenamento seguro do token, expiração e reCAPTCHA.
PWA Studio vs headless customizado no Magento: visão de dev
PWA Studio vs um frontend headless customizado no Magento: arquitetura, custos reais, lacunas do GraphQL, SEO, checkout e quando um theme server rendered vence.
Cache e performance do GraphQL no Magento 2: guia prático
Deixe o GraphQL do Magento 2 rápido: queries GET, cache no Varnish, X-Magento-Cache-Id, identities do @cache em resolvers customizados, batching de N+1 e limites de complexidade.
GraphQL no Magento 2: adicione uma query e um resolver customizados
Como adicionar uma query GraphQL customizada no Magento 2.4: schema.graphqls, resolver com ResolverInterface, erros, checagem de auth e cache identity para o Varnish.