← Voltar ao blog

Checkout GraphQL no Magento 2: das mutations do carrinho ao pedido

Rodrigo Biazzi

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

  1. Obter um cart ID: criar um carrinho de visitante ou ler o carrinho do cliente.
  2. Adicionar produtos.
  3. Definir o e-mail do visitante (só para visitantes).
  4. Definir o endereço de entrega e ler os métodos de frete disponíveis.
  5. Definir o endereço de cobrança.
  6. Definir o método de frete.
  7. Ler os meios de pagamento disponíveis e definir um.
  8. 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