Integrações

Pedido duplicado vindo do ERP? Use o campo ext_order_id

O middleware manda o pedido, o Magento demora 40 segundos para responder, o middleware acha que falhou e manda de novo. Dois pedidos, um cliente, uma dor de cabeça no estoque.

Por Roger Takemiya · Publicado em · 5 min de leitura

Pedido duplicado quase nunca é bug do Magento. É timeout. O middleware manda o pedido, o Magento leva 40 segundos gravando, a conexão estoura antes da resposta, e a fila do middleware faz o que foi programada para fazer: tenta de novo. O primeiro pedido entrou. O segundo também.

A correção não é aumentar o timeout. É dar ao Magento uma forma de reconhecer que aquele pedido já chegou. E existe campo nativo para isso desde sempre.

O campo que já está lá

Abra o db_schema.xml do Magento_Sales e você acha isto na tabela sales_order:

<column xsi:type="varchar" name="ext_order_id" nullable="true" length="32"/>

E, algumas linhas abaixo, um índice dedicado, o SALES_ORDER_EXT_ORDER_ID, do tipo btree. Ou seja: consultar por esse campo em tabela com meio milhão de pedidos é rápido, não é varredura.

Na API ele aparece como ext_order_id, com getExtOrderId() e setExtOrderId() declarados na OrderInterface. Não é campo de extensão, não é atributo customizado: é parte do contrato oficial do pedido.

Uma limitação para já anotar: são 32 caracteres. UUID com hífen tem 36 e não cabe. Use o número do pedido do ERP, ou o UUID sem hífen, ou um hash MD5 da chave que o seu sistema considera única.

Consulte antes de criar

Essa é a parte que faz o trabalho. Antes de montar qualquer payload, o middleware pergunta se aquele pedido já existe:

curl -s -H "Authorization: Bearer $TOKEN" \
 "https://loja.com.br/rest/V1/orders?searchCriteria[filter_groups][0][filters][0][field]=ext_order_id&searchCriteria[filter_groups][0][filters][0][value]=ERP-000123&searchCriteria[filter_groups][0][filters][0][condition_type]=eq"

A resposta traz total_count. Se vier 0, o pedido nunca chegou e pode criar. Se vier 1, o retry acabou de virar uma consulta barata, e o items[0].increment_id é o número que o ERP precisava receber na primeira tentativa.

Dá para enxugar a resposta com &fields=total_count,items[entity_id,increment_id], o que ajuda bastante quando o pedido tem cinquenta itens e você só quer saber se existe.

Grave o ID na hora de criar

Se a sua integração cria o pedido pelo POST /V1/orders, que é o caminho de quem recebe pedido fechado de fora (hub de marketplace, televendas, PDV), o campo entra junto com o resto da entidade:

{
  "entity": {
    "ext_order_id": "ERP-000123",
    "customer_email": "[email protected]",
    "items": []
  }
}

Se o pedido nasce pelo fluxo de carrinho — POST /V1/carts e depois o PUT que fecha o pedido —, o ext_order_id não faz parte desse payload. Aí a gravação precisa de um observer curto no evento sales_order_place_after, copiando o identificador que você carregou no carrinho. É a única parte da história que pede código.

Em qualquer um dos dois casos, grave o ID na mesma transação em que o pedido é criado. Gravar depois, num segundo request, reabre exatamente a janela que você está tentando fechar.

Onde isso falha

Nenhuma solução de idempotência é de graça, e essa tem três limites que vale conhecer antes de confiar cegamente.

O índice não é único. É um btree comum, criado para busca, não uma constraint. Ele deixa gravar dois pedidos com o mesmo ext_order_id sem reclamar. Quem impede a duplicata é a sua consulta, não o banco.

Consulta e criação não são atômicas. Se dois retries saem ao mesmo tempo, os dois consultam, os dois recebem zero e os dois criam. Se o seu middleware tem paralelismo, resolva com uma trava do lado dele — chave no Redis, lock na tabela de fila, o que já existir aí.

O admin não mostra esse campo. A tabela sales_order_grid não tem a coluna ext_order_id, então não adianta procurar no filtro do grid de pedidos. Ou você consulta pela API, ou por SQL, ou adiciona a coluna ao grid num módulo. Como montar a integração inteira sem se afogar em retry está no guia de integração com ERP e NF-e.

Perguntas rápidas

Posso usar o increment_id do Magento como chave de idempotência?

Não serve para esse caso. O increment_id é gerado pelo Magento na criação, então o middleware só descobre o valor depois que o pedido já existe. A chave precisa ser conhecida antes do primeiro envio, e por isso ela vem do lado do ERP.

E se o ERP mandar o mesmo pedido com valores diferentes?

Aí não é retry, é atualização, e a resposta certa não é criar nem ignorar. Consulte pelo ext_order_id, encontre o pedido e trate a diferença como cancelamento e novo pedido, ou como edição, conforme a regra do negócio.

Dá para transformar o índice em UNIQUE?

Tecnicamente sim, com um db_schema.xml de módulo próprio. Só entenda o efeito: qualquer pedido criado sem preencher o campo grava NULL, e no MySQL vários NULL convivem num índice único, então a proteção só vale para quem preenche.

Pra conferir na fonte

  1. OrderInterface.php — constante EXT_ORDER_ID — GitHub
  2. Magento_Sales db_schema.xml — coluna e índice ext_order_id — GitHub
  3. Search using REST endpoints — Adobe Developer
  4. Magento_Sales webapi.xml — rotas de pedido — GitHub

erp api pedido

Precisa de um orçamento? Ficarei feliz em ajudar. Clique Aqui