PT-PT

Guia de arquitetura de integração WooCommerce ERP 2026

Última verificação: 24 de agosto de 2026
34 min de leitura
Guia
Especialista WooCommerce

A integração de uma loja digital empresarial em WooCommerce com um sistema de planeamento de recursos empresariais (ERP) representa um dos maiores desafios de engenharia de software no comércio eletrónico moderno. Quando o catálogo de artigos ultrapassa as cinquenta mil unidades de manutenção de stock (SKUs), o volume diário atinge milhares de encomendas e a atualização de inventário ocorre em tempo real através de múltiplos canais de venda, as integrações síncronas ponto a ponto tradicionais colapsam. As chamadas HTTP diretas entre sistemas provocam tempos de espera em cascata, bloqueios na base de dados, tempestades de webhooks e situações graves de sobrevenda de produtos no checkout.

Resposta direta: Como arquitetar uma integração bidirecional e tolerante a falhas entre WooCommerce e ERP

Uma integração bidirecional fiável entre o WooCommerce e um sistema ERP requer uma arquitetura desacoplada, assíncrona e orientada a eventos estruturada em cinco pilares fundamentais de engenharia:

  • Armazenamento assíncrono em fila de espera: Receção de webhooks e eventos de compra numa fila intermédia (Redis Streams ou Cloudflare Queues) com resposta imediata HTTP 202 Accepted em menos de 25 ms, desvinculando o funcionamento da loja da latência de resposta do ERP.
  • Garantia de idempotência: Aplicação de deduplicação rigorosa através do cabeçalho X-Idempotency-Key e de bloqueios atómicos distribuídos no Redis para eliminar encomendas repetidas e lançamentos financeiros duplicados.
  • Controlo de concorrência transacional: Utilização de bloqueios de linha em MySQL InnoDB (SELECT ... FOR UPDATE) ou controlo de versões otimista para prevenir condições de corrida e sobrevenda durante períodos de pico de tráfego.
  • Gestão resiliente de erros: Implementação de recuo exponencial com dispersão aleatória (full jitter) e encaminhamento de cargas com falhas persistentes para uma Dead Letter Queue (DLQ) para diagnóstico e reprocessamento.
  • Reconciliação em dois níveis: Combinação de sincronizações delta horárias contínuas com auditorias noturnas de somas de verificação criptográficas (hashes SHA-256 por blocos) para detetar e corrigir desvios de dados.
SLA de ingestão: Menos de 25 ms em webhooks não bloqueantes
Modelo de consistência: Consistência eventual com bloqueios atómicos
Estratégia de recuperação: Dead Letter Queue com rotinas de repetição

Para equipas de engenharia e diretores técnicos que pretendem conceber ou modernizar uma infraestrutura de comércio eletrónico, os nossos serviços de integração WooCommerce ERP oferecem apoio especializado em arquitetura e implementação. Este guia analisa os modelos arquitetónicos, as matrizes de compatibilidade das principais plataformas ERP, as implementações práticas em PHP 8.4 e os manuais operacionais de resolução de incidentes em ambientes de produção.

#Fundamentos de arquitetura: Integração desacoplada orientada a eventos

Em arquiteturas convencionais de WooCommerce, os eventos da loja desencadeiam pedidos HTTP diretos para o ponto de contacto do ERP. Quando um cliente finaliza uma compra, o gancho woocommerce_checkout_order_processed é executado, iniciando uma ligação cURL síncrona para o SAP S/4HANA, Microsoft Dynamics 365 ou Comarch Optima.

Este padrão síncrono introduz uma vulnerabilidade crítica no sistema:

[Navegador do cliente] 
      │ (1) Submeter encomenda no checkout

[WooCommerce / PHP-FPM Worker] ──(2) POST HTTP síncrono──▶ [Ponto de contacto ERP (Lento / Indisponível)]
      │                                                               │
      │ ◀───(3) HTTP 504 Gateway Timeout (Thread bloqueada por 60s)───┘

[Cliente vê ecrã de erro] ──▶ Cliques repetidos ──▶ Deadlocks na BD e encomendas duplicadas

Quando o sistema ERP efetua processamento noturno em lote, rotinas de manutenção na base de dados ou regista picos de latência na rede, os tempos de resposta disparam de 200 milissegundos para trinta segundos ou mais. Uma vez que os processos PHP-FPM ficam bloqueados à espera da resposta do socket de rede, a reserva de threads do servidor web esgota-se rapidamente. Os novos utilizadores que tentam navegar pelo catálogo ou aceder ao carrinho recebem de imediato mensagens de erro HTTP 504 Gateway Timeout. Adicionalmente, caso a ligação falhe após o ERP ter gravado o registo, mas antes de o WooCommerce receber a confirmação, os mecanismos de repetição automáticos criarão faturas duplicadas e reservas de stock em duplicado.

#O padrão de intermediário de eventos desacoplado

Garantir a fiabilidade ao nível empresarial exige separar a produção de eventos do seu processamento efetivo. A loja virtual e o sistema ERP comunicam exclusivamente através de um intermediário de mensagens (event broker).

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                       PIPELINE DESACOPLADO ORIENTADO A EVENTOS                          │
│ (Desacoplamento total entre o checkout da loja e o processamento no sistema ERP)        │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 ┌─────────────────────────┐                     ┌─────────────────────────┐
 │    Loja WooCommerce     │                     │       Sistema ERP       │
 │ (Hub de encomendas/dados│                     │ (Stock mestre e contas) │
 └────────────┬────────────┘                     └────────────▲────────────┘
              │                                               │
    (1) Ingestão rápida                             (4) Envio controlado
       (Não bloqueante)                                (Ajustado a quotas)
              ▼                                               │
 ┌────────────────────────────────────────────────────────────┴───────────────────────────┐
 │                              BROKER DE MENSAGENS E BUFFER                              │
 │                          (Redis Streams / Cloudflare Queues)                           │
 │                                                                                        │
 │   ┌──────────────────────┐  ┌──────────────────────┐  ┌────────────────────────────┐   │
 │   │  orders.incoming     │  │  inventory.delta     │  │  dead.letter.queue (DLQ)   │   │
 │   │  [Evento 1][Evento 2]│  │  [SKU 101][SKU 102]  │  │  [Cargas com falha + logs] │   │
 │   └──────────┬───────────┘  └──────────┬───────────┘  └─────────────▲──────────────┘   │
 └──────────────┼─────────────────────────┼────────────────────────────┼──────────────────┘
                │                         │                            │
      (2) Leitura do stream     (5) Atualização de stock      (3) Limite de
         por grupo consumidor      com bloqueios InnoDB          tentativas excedido
                ▼                         ▼                            │
 ┌─────────────────────────────────────────────────────────────────────┴──────────────────┐
 │                           CONJUNTO DE DAEMONS WP-CLI (PHP 8.4)                         │
 │                                                                                        │
 │  - Gestão de sinais POSIX (SIGTERM/SIGINT) - Validação de idempotência (SETNX)         │
 │  - Gestão de memória (wp_cache_flush)      - Recuo exponencial com jitter              │
 └────────────────────────────────────────────────────────────────────────────────────────┘
  1. Registo imediato de eventos: Quando ocorre uma compra, o WooCommerce escreve uma mensagem compacta num fluxo do Redis (orders.incoming) e apresenta a confirmação ao cliente em menos de 15 milissegundos.
  2. Processamento assíncrono em segundo plano: Um grupo de processos monitorizados executados via WP-CLI consome as mensagens da fila de acordo com a capacidade de processamento do ERP.
  3. Contrapressão controlada (backpressure): Se o ERP restringir as ligações de entrada ou entrar em manutenção, as mensagens acumulam-se com segurança no buffer do Redis sem degradar o desempenho da loja.
  4. Execução idempotente: Cada mensagem possui uma chave de idempotência determinista única. Se um processo de trabalho for interrompido, o processo seguinte retoma a operação a partir da lista de tarefas pendentes sem duplicar dados na base de dados.

#Matriz de sistemas ERP e compatibilidade de protocolos

As diferentes plataformas de planeamento de recursos empresariais possuem modelos de rede, restrições de concorrência, protocolos de autenticação e taxas de transferência distintos. Uma integração bem-sucedida implica adaptar a camada de comunicação a cada sistema em concreto.

A tabela seguinte compara quatro sistemas ERP representativos integrados frequentemente com o WooCommerce no mercado europeu e internacional:

Plataforma ERPProtocolos nativosPerfil de débitoModelo de concorrência e bloqueioPrincipais modos de falha arquitetónica
SAP S/4HANAOData v4, IDoc via SAP BTP, RFC, Async SOAPElevado débito em lote (10k+ registos/min); latência média em chamadas individuaisSAP Logical Unit of Work (LUW); bloqueios no Enqueue Server; limites de lote em ODataSaturação da pool de ligações ao reiniciar o SAP Cloud Connector; tempos de espera em estruturas BOM complexas
Comarch Optima / XLOptima WebAPI, automação COM DLL, tabelas staging em MS SQLDébito API moderado (50-100 encomendas/min); muito elevado via SQL staging (50k/min)Single-Threaded Apartment (STA) em COM; escalonamento de bloqueios no MS SQL (sp_lock)Fugas de memória em processos COM a exigir reinicializações; bloqueios de licenças em threads presas
InsERT Subiekt GT / nexoSubiekt GT Sfera (COM/OLE), Subiekt nexo PRO SDK (.NET / WebAPI)GT: 30-80 encomendas/min; nexo PRO: 250+ encomendas/minBloqueio de linhas no SQL Server em dok__Dokument e tw__Towar; constrangimentos de desktop COMBloqueio de threads COM por janelas de diálogo modais no GT; fragmentação de índices com catálogos extensos
Microsoft Dynamics 365 BCBusiness Central REST API v2.0, OData v4, AL API PagesLimite de 600 pedidos/min na nuvem; processamento em lote JSON (100 subpedidos)Isolamento por snapshot no Azure SQL; bloqueios no cabeçalho de vendas ao registarThrottling HTTP 429; perda de notificações de webhook durante a emissão massiva de faturas

#Padrões de integração para SAP S/4HANA

Em ambientes SAP S/4HANA, a ligação é normalmente efetuada através da plataforma SAP Business Technology Platform (SAP BTP) e do SAP Cloud Connector. As chamadas síncronas individuais através de serviços padrão OData v4 (API_SALES_ORDER_SRV) apresentam uma latência de rede média de 450 a 800 milissegundos por chamada.

Para lojas com grande volume de vendas, é indispensável utilizar processos assíncronos em lote:

  • Agrupamento JSON (Batching): Agregar até cem encomendas num único pedido multipart OData. Isto diminui o impacto dos handshakes TCP e assegura que todos os documentos sejam processados de forma atómica.
  • Filas intermediárias IDoc: Para importações massivas de catálogo ou atualizações globais de preços, recorre-se a interfaces IDoc (como ORDERS05 ou MATMAS05) processadas em segundo plano no SAP.
  • Gestão de unidades lógicas (SAP LUW): O SAP assegura a consistência através de Logical Units of Work. A camada de integração deve guardar o número de documento devolvido pelo SAP numa tabela de correspondências (wp_wc_orders_erp_lookup), associando-o ao ID da encomenda no WooCommerce.

#Comarch ERP Optima e Comarch ERP XL

O Comarch Optima é uma solução muito utilizada em empresas de média dimensão. Tendo sido concebido originalmente para correr em Microsoft SQL Server em redes locais Windows, a integração moderna através de APIs coloca desafios específicos.

  • WebAPI versus automação COM: Embora a Optima WebAPI disponibilize serviços REST, internamente instancia objetos COM (Optima.dll). Isto obriga a inicializar cada thread no modelo Single-Threaded Apartment (STA) através de CoInitialize.
  • Gestão de licenças de postos: A execução simultânea de múltiplos processos PHP sem controlo de filas esgota os postos de licença do módulo de integração, originando rejeições imediatas.
  • Arquitetura de tabelas de staging: Para plataformas B2B com dezenas de milhares de alterações de stock por hora, a arquitetura mais fiável passa por replicar os dados de inventário e preços a partir de snapshots de leitura do SQL Server para o Redis, enquanto a criação de encomendas é entregue a daemons dedicados em Windows com execução sequencial.

#InsERT Subiekt GT e Subiekt nexo PRO

O InsERT Subiekt GT utiliza a biblioteca Sfera para o Subiekt GT (uma interface COM/OLE Automation), enquanto o Subiekt nexo PRO oferece um SDK completo em .NET e serviços WebAPI modernos.

  • Limitações do Subiekt GT Sfera: As chamadas para a Sfera correm de forma síncrona no ambiente de trabalho do Windows. Caso ocorra um erro não tratado ou surja uma caixa de diálogo modal no sistema, a thread COM fica bloqueada indefinidamente. O serviço de integração deve funcionar como um serviço gerido de Windows (em C# ou Go) com monitorização ativa e limites rígidos de 30 segundos.
  • Vantagens do Subiekt nexo PRO: O nexo PRO permite operações assíncronas e multithread. Ao sincronizar produtos, deve filtrar-se as alterações com base na coluna de carimbo temporal Zmieniono, transferindo apenas os registos posteriores ao último ponto de controlo.

#Microsoft Dynamics 365 Business Central

A versão em nuvem do Business Central aplica regras estritas de utilização de recursos:

  • Limites de pedidos (Throttling): A Microsoft limita os pedidos da API a 600 chamadas por minuto por ambiente. A ultrapassagem deste valor resulta de imediato num código HTTP 429 com cabeçalho Retry-After.
  • Pontos de contacto $batch: Para submeter cinquenta encomendas sem realizar cinquenta pedidos HTTP independentes, envia-se um pedido POST para https://api.businesscentral.dynamics.com/v2.0/{tenant}/production/api/v2.0/$batch com um array de subpedidos processados sequencialmente na nuvem.
  • Captura de alterações (Change Data Capture): Em vez de consultar continuamente o inventário (polling), criam-se subscrições de webhooks no Business Central (notificações do Graph) para despoletar sincronizações pontuais mediante receção de avisos.

#Padrões de tolerância a falhas para transações de elevado volume

As integrações empresariais devem ser desenhadas assumindo que ligações de rede, bases de dados e serviços externos falharão de forma intermitente. Cinco padrões arquitetónicos asseguram a continuidade operacional.

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                      PADRÃO DE IDEMPOTÊNCIA E TRATAMENTO DE ERROS                       │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 Pedido recebido / Webhook


 ┌───────────────────────────────────┐
 │ Extrair cabeçalho                 │
 │ X-Idempotency-Key                 │
 └─────────────────┬─────────────────┘


 ┌───────────────────────────────────┐
 │ Bloqueio atómico no Redis:        │
 │ SETNX lock:idempotency:{key}      │
 └─────────┬─────────────────────────┘

     ┌─────┴────────────────────────┐
     │ Chave obtida (Novo pedido)   │ Chave existente (Duplicado / Em curso)
     ▼                              ▼
 ┌───────────────────────────┐  ┌─────────────────────────────────────────────────┐
 │ Estado: PROCESSING        │  │ Verificar estado:                               │
 │ (TTL: 86400 segundos)     │  │ - Se 'PROCESSING': Devolver HTTP 409 Conflict  │
 └─────────┬─────────────────┘  │ - Se 'COMPLETED': Devolver resposta em cache    │
           │                    └─────────────────────────────────────────────────┘

 ┌───────────────────────────┐
 │ Executar lógica de negócio│
 │ (Transação + Escrita BD)  │
 └─────────┬─────────────────┘

     ┌─────┴────────────────────────┐
     │ Sucesso                      │ Falha (Exceção / Timeout)
     ▼                              ▼
 ┌───────────────────────────┐  ┌─────────────────────────────────────────────────┐
 │ Atualizar estado no Redis:│  │ Calcular recuo exponencial com jitter:          │
 │ COMPLETED + Cache Payload │  │ sleep = min(T_max, T_base * 2^tentativa) + rand │
 └─────────┬─────────────────┘  └─────────────┬───────────────────────────────────┘
           │                                  │
           ▼                                  ▼
 ┌───────────────────────────┐  ┌─────────────────────────────────────────────────┐
 │ Devolver HTTP 200/201     │  │ Se tentativas < 5: Reencaminhar para fila difer │
 └───────────────────────────┘  │ Se tentativas >= 5: Enviar para Dead Letter Q   │
                                └─────────────────────────────────────────────────┘

#1. Processamento de pedidos idempotentes (X-Idempotency-Key)

Em sistemas assíncronos, repetições de mensagens na rede e webhooks duplicados são ocorrências normais. Sem salvaguardas de idempotência, o reprocessamento de um webhook de confirmação de pagamento poderia originar duas guias de remessa ou efetuar dois reembolsos ao cliente.

Qualquer pedido de escrita deve conter uma chave única:

  • Estrutura da chave: O cliente envia o cabeçalho X-Idempotency-Key (UUIDv4) ou o servidor gera um hash determinista: sha256(order_id + status + timestamp).
  • Bloqueio atómico de estado: Antes de processar a carga, o processo de trabalho efetua um registo atómico no Redis:
    SET lock:idempotency:{hash} "PROCESSING" NX EX 86400
  • Resolução de estados:
    • Se a chave for guardada com sucesso (OK), a operação é executada. No final, o valor é atualizado para "COMPLETED:{json_resposta}".
    • Se a chave já existir com o valor "PROCESSING", outro processo está a tratar o pedido. A solicitação duplicada devolve HTTP 409 Conflict ou aguarda libertação.
    • Se o valor começar por "COMPLETED:", o processo ignora a execução e entrega de imediato a resposta guardada em cache com o cabeçalho X-Cache-Lookup: HIT.

#2. Redis Streams para ordenação fiável de eventos

As listas simples no Redis (LPUSH/RPOP) perdem mensagens caso um processo de trabalho falhe após retirar a tarefa, mas antes de concluir a transação na base de dados. O Redis Streams elimina esta fragilidade com grupos de consumidores:

  • Inserção de mensagens (XADD): Os eventos são guardados num registo permanente com carimbos temporais em milissegundos.
  • Grupos de consumidores (XREADGROUP): Vários processos distribuem a carga de um mesmo fluxo sem duplicar trabalho. O Redis regista que processo está a tratar cada mensagem.
  • Confirmação explícita (XACK): A mensagem apenas é removida da lista de mensagens pendentes (PEL) quando o processo confirma todas as alterações na base de dados e chama XACK.
  • Recuperação de mensagens órfãs (XCLAIM): Caso um processo seja encerrado de forma inesperada, os outros processos inspecionam a lista PEL através de XPENDING e recuperam com XCLAIM as mensagens paradas há mais de 60 segundos.

#3. Filas de mensagens não entregues (DLQ) e recuo exponencial com dispersão aleatória (full jitter)

Os erros de integração devem ser organizados em duas categorias:

  1. Erros transitórios: Quebras pontuais de rede, erros HTTP 502/503/504, limites de pedidos HTTP 429 e deadlocks temporários no MySQL. Estes devem ser repetidos automaticamente.
  2. Erros permanentes: Erros de validação HTTP 400, recursos não encontrados 404, entidades não processáveis 422 ou incorreções de taxas fiscais. Não devem ser repetidos automaticamente.

Para falhas transitórias, aplica-se o algoritmo de recuo exponencial com dispersão aleatória (full jitter), evitando que dezenas de processos sobrecarreguem em simultâneo um servidor ERP em fase de recuperação:

$$\text{Intervalo} = \min\left(T_{\text{max}}, T_{\text{base}} \times 2^{\text{tentativa}}\right)$$

$$\text{Tempo de espera} = \text{random}\left(0, \text{Intervalo}\right)$$

Se uma mensagem falhar após cinco tentativas, é movida para a Dead Letter Queue (dlq:erp_sync). O registo na DLQ contém a carga completa, o rastreio da exceção, o código de estado HTTP e o carimbo de data/hora para análise e posterior reprocessamento.


#Controlo de concorrência na base de dados e bloqueio de inventário

Durante promoções especiais ou lançamentos de produtos exclusivos, centenas de utilizadores tentam comprar as mesmas unidades em poucos segundos. Se duas transações lerem o stock simultaneamente, confirmarem disponibilidade e decrementarem valores em paralelo, a loja venderá artigos sem existência física real.

#Bloqueio pessimista: MySQL InnoDB SELECT FOR UPDATE

O WooCommerce armazena as quantidades de stock na base de dados MySQL. A utilização das funções padrão get_stock_quantity() e wc_update_product_stock() em ambientes concorrentes não é segura, uma vez que executam leituras não bloqueantes.

O bloqueio pessimista resolve este problema bloqueando a linha da base de dados durante toda a transação:

Transação A (Cliente 1)                         Transação B (Cliente 2)
───────────────────────                         ───────────────────────
START TRANSACTION;                              START TRANSACTION;

SELECT stock_quantity                           SELECT stock_quantity
FROM wp_wc_product_meta                         FROM wp_wc_product_meta
WHERE product_id = 4500                         WHERE product_id = 4500
FOR UPDATE;                                     FOR UPDATE;
──▶ Linha bloqueada pela Transação A                ──▶ EM ESPERA (Thread bloqueada)

Verificar stock: 5 disponíveis.
Deduzir: 5 - 1 = 4.

UPDATE wp_wc_product_meta
SET stock_quantity = 4
WHERE product_id = 4500;

COMMIT; ── Liberta o bloqueio ────────────────▶ Bloqueio concedido à Transação B!
                                                Verificar stock: 4 disponíveis.
                                                Deduzir: 4 - 1 = 3.
                                                UPDATE wp_wc_product_meta SET ...
                                                COMMIT;

Princípio arquitetónico fundamental: Nunca execute um pedido HTTP externo para o ERP com uma transação de base de dados aberta. Bloqueie a linha, atualize as tabelas locais, finalize a transação em menos de 50 milissegundos e envie o evento de sincronização para a fila assíncrona.

#Controlo de concorrência otimista (OCC)

Quando o volume de escrita é moderado, o controlo otimista oferece maior desempenho sem bloquear leituras.

Adiciona-se uma coluna de versão (version) na tabela de metadados:

UPDATE wp_wc_product_meta 
SET stock_quantity = stock_quantity - :quantidade_compra,
    version = version + 1
WHERE product_id = :produto_id 
  AND version = :versao_esperada 
  AND stock_quantity >= :quantidade_compra;

Se outro processo tiver alterado o produto entretanto, a condição de versão falhará e nenhuma linha será atualizada. A aplicação deteta este facto e repete o procedimento com o novo número de versão.


#Listagens de código de produção: Consumidor de filas em PHP 8.4 e daemon WP-CLI

Os seguintes componentes representam soluções testadas em produção, preparadas para ambientes WordPress e WooCommerce em PHP 8.4.

#Listagem 1: Daemon consumidor de filas em segundo plano (QueueConsumerCommand.php)

Este comando WP-CLI é executado como um serviço de sistema permanente sob gestão do Systemd ou Supervisord. Processa fluxos de Redis Streams, consome mensagens em lote, gere sinais POSIX e efetua a reciclagem de memória.

<?php
declare(strict_types=1);

namespace WPPoland\ErpIntegration\Cli;

use WP_CLI;
use Redis;
use Throwable;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Daemon supervisionado de WP-CLI para sincronização assíncrona entre WooCommerce e ERP.
 */
class QueueConsumerCommand
{
    private const STREAM_KEY = 'erp:stream:orders';
    private const CONSUMER_GROUP = 'erp_sync_group';
    private const BATCH_SIZE = 10;
    private const BLOCK_TIMEOUT_MS = 2000;
    private const MAX_MEMORY_BYTES = 134217728; // Limite de 128 MB antes de reinício controlado

    private Redis $redis;
    private string $consumerName;
    private bool $shouldRun = true;

    public function __construct()
    {
        $this->consumerName = 'worker_' . gethostname() . '_' . getmypid();
        $this->initRedis();
        $this->registerSignalHandlers();
    }

    /**
     * Ponto de entrada: wp erp-queue consume
     */
    public function __invoke(array $args, array $assocArgs): void
    {
        WP_CLI::line("A iniciar daemon consumidor de filas ERP [{$this->consumerName}] em PHP " . PHP_VERSION);
        $this->ensureConsumerGroup();

        $processedCount = 0;

        while ($this->shouldRun) {
            // Processamento de sinais POSIX
            if (function_exists('pcntl_signal_dispatch')) {
                pcntl_signal_dispatch();
            }

            try {
                $messages = $this->redis->xReadGroup(
                    self::CONSUMER_GROUP,
                    $this->consumerName,
                    [self::STREAM_KEY => '>'],
                    self::BATCH_SIZE,
                    self::BLOCK_TIMEOUT_MS
                );

                if (empty($messages) || !isset($messages[self::STREAM_KEY])) {
                    $this->reclaimOrphanedMessages();
                    $this->checkMemoryThreshold();
                    continue;
                }

                foreach ($messages[self::STREAM_KEY] as $messageId => $payload) {
                    $this->processMessage((string) $messageId, $payload);
                    $processedCount++;
                }

                $this->checkMemoryThreshold();
            } catch (Throwable $e) {
                WP_CLI::error("Exceção não tratada no ciclo consumidor: " . $e->getMessage(), false);
                sleep(2); // Pausa preventiva após falha de infraestrutura
            }
        }

        WP_CLI::success("Consumidor de filas finalizado após processar {$processedCount} mensagens.");
    }

    private function processMessage(string $messageId, array $payload): void
    {
        $orderId = isset($payload['order_id']) ? (int) $payload['order_id'] : 0;
        $idempotencyKey = $payload['idempotency_key'] ?? '';

        if ($orderId <= 0 || empty($idempotencyKey)) {
            WP_CLI::warning("Dados inválidos na mensagem {$messageId}. A reencaminhar para DLQ.");
            $this->routeToDlq($messageId, $payload, 'Erro de validação: falta order_id ou idempotency_key');
            $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            return;
        }

        // Verificação de bloqueio de idempotência no Redis
        $lockKey = "erp:lock:idemp:{$idempotencyKey}";
        $acquired = $this->redis->set($lockKey, 'PROCESSING', ['NX', 'EX' => 86400]);

        if (!$acquired) {
            $status = (string) $this->redis->get($lockKey);
            if (str_starts_with($status, 'COMPLETED')) {
                WP_CLI::line("O evento {$idempotencyKey} já se encontra concluído. A confirmar.");
                $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
                return;
            }
            WP_CLI::line("O evento {$idempotencyKey} está a ser processado por outro processo. A ignorar.");
            return;
        }

        try {
            // Execução da sincronização com o ERP
            $this->syncOrderToErp($orderId, $payload);

            // Marcação como concluído e confirmação no fluxo
            $this->redis->set($lockKey, 'COMPLETED:' . time(), ['EX' => 86400]);
            $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            WP_CLI::line("Encomenda #{$orderId} sincronizada com sucesso [Msg: {$messageId}]");
        } catch (Throwable $e) {
            WP_CLI::warning("Falha ao sincronizar encomenda #{$orderId}: " . $e->getMessage());
            $this->redis->del($lockKey); // Libertar bloqueio para repetição

            $attempts = isset($payload['_retry_count']) ? ((int) $payload['_retry_count']) + 1 : 1;
            if ($attempts >= 5) {
                $this->routeToDlq($messageId, $payload, $e->getMessage());
                $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            } else {
                // Reencaminhar para a fila com contador incrementado
                $payload['_retry_count'] = $attempts;
                $this->redis->xAdd(self::STREAM_KEY, '*', $payload);
                $this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
            }
        }
    }

    private function syncOrderToErp(int $orderId, array $payload): void
    {
        $order = wc_get_order($orderId);
        if (!$order) {
            throw new \RuntimeException("A encomenda WooCommerce #{$orderId} não foi encontrada na base de dados.");
        }

        // Exemplo: Invocar cliente de integração do ERP
        // $this->erpClient->createSalesOrder($order);
    }

    private function reclaimOrphanedMessages(): void
    {
        // Pesquisa de mensagens pendentes há mais de 60 segundos
        $pending = $this->redis->xPending(self::STREAM_KEY, self::CONSUMER_GROUP, '-', '+', 5);
        if (empty($pending)) {
            return;
        }

        $staleIds = [];
        foreach ($pending as $entry) {
            $messageId = $entry[0];
            $idleMs = $entry[2];
            if ($idleMs > 60000) {
                $staleIds[] = $messageId;
            }
        }

        if (!empty($staleIds)) {
            $claimed = $this->redis->xClaim(
                self::STREAM_KEY,
                self::CONSUMER_GROUP,
                $this->consumerName,
                60000,
                $staleIds,
                ['JUSTID']
            );
            WP_CLI::line("Recuperadas " . count($claimed) . " mensagens órfãs de processos inativos.");
        }
    }

    private function routeToDlq(string $messageId, array $payload, string $reason): void
    {
        $dlqEntry = [
            'original_id' => $messageId,
            'payload' => json_encode($payload, JSON_THROW_ON_ERROR),
            'failure_reason' => $reason,
            'failed_at' => (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format(\DateTimeInterface::ATOM),
            'consumer' => $this->consumerName,
        ];
        $this->redis->xAdd('erp:stream:dlq', '*', $dlqEntry);
        WP_CLI::error("Mensagem {$messageId} transferida para DLQ: {$reason}", false);
    }

    private function checkMemoryThreshold(): void
    {
        // Limpeza de cache de objetos do WordPress e do registo de queries SQL
        wp_cache_flush();
        global $wpdb;
        $wpdb->queries = [];
        if (function_exists('gc_collect_cycles')) {
            gc_collect_cycles();
        }

        $memoryUsed = memory_get_usage(true);
        if ($memoryUsed >= self::MAX_MEMORY_BYTES) {
            WP_CLI::line("Limite de memória atingido (" . round($memoryUsed / 1048576, 2) . " MB). A reiniciar processo...");
            $this->shouldRun = false;
        }
    }

    private function registerSignalHandlers(): void
    {
        if (!function_exists('pcntl_signal')) {
            return;
        }
        pcntl_signal(SIGTERM, function () {
            WP_CLI::line("Sinal SIGTERM recebido. A concluir lote atual antes de sair...");
            $this->shouldRun = false;
        });
        pcntl_signal(SIGINT, function () {
            WP_CLI::line("Sinal SIGINT recebido. A desligar...");
            $this->shouldRun = false;
        });
    }

    private function initRedis(): void
    {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379, 2.5);
    }

    private function ensureConsumerGroup(): void
    {
        try {
            $this->redis->xGroup('CREATE', self::STREAM_KEY, self::CONSUMER_GROUP, '0', true);
        } catch (Throwable) {
            // Grupo já existe no fluxo
        }
    }
}

#Listagem 2: Ponto de entrada de webhook seguro com validação HMAC (WebhookController.php)

Este controlador de REST API recebe notificações do ERP (como variações de inventário), valida assinaturas criptográficas SHA-256 HMAC em tempo constante, verifica carimbos de data/hora e regista os eventos no Redis Streams em menos de 20 milissegundos.

<?php
declare(strict_types=1);

namespace WPPoland\ErpIntegration\Api;

use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use Redis;
use Throwable;

if (!defined('ABSPATH')) {
    exit;
}

class WebhookController extends WP_REST_Controller
{
    protected $namespace = 'erp-sync/v1';
    protected $rest_base = 'webhook';

    private const WEBHOOK_SECRET_OPTION = 'erp_webhook_hmac_secret';
    private const MAX_TIMESTAMP_SKEW_SECONDS = 300; // Tolerância de 5 minutos contra ataques de repetição

    public function register_routes(): void
    {
        register_rest_route($this->namespace, '/' . $this->rest_base, [
            [
                'methods' => 'POST',
                'callback' => [$this, 'handleIncomingWebhook'],
                'permission_callback' => [$this, 'validateHmacSignature'],
            ],
        ]);
    }

    /**
     * Validação segura de assinatura HMAC e frescura do carimbo temporal.
     */
    public function validateHmacSignature(WP_REST_Request $request): bool|WP_Error
    {
        $signatureHeader = $request->get_header('x-erp-signature-256');
        $timestampHeader = $request->get_header('x-erp-timestamp');

        if (empty($signatureHeader) || empty($timestampHeader)) {
            return new WP_Error(
                'rest_forbidden',
                'Faltam cabeçalhos de autenticação obrigatórios: X-ERP-Signature-256 ou X-ERP-Timestamp.',
                ['status' => 401]
            );
        }

        // Validação do carimbo temporal para evitar repetição
        $requestTime = (int) $timestampHeader;
        $currentTime = time();
        if (abs($currentTime - $requestTime) > self::MAX_TIMESTAMP_SKEW_SECONDS) {
            return new WP_Error(
                'rest_forbidden',
                'O carimbo temporal do webhook ultrapassa o desvio máximo permitido (300 s).',
                ['status' => 403]
            );
        }

        $rawBody = $request->get_body();
        $secret = (string) get_option(self::WEBHOOK_SECRET_OPTION, '');

        if (empty($secret)) {
            return new WP_Error('rest_error', 'Segredo HMAC não configurado no servidor.', ['status' => 500]);
        }

        $signedPayload = "t={$timestampHeader}.{$rawBody}";
        $expectedSignature = hash_hmac('sha256', $signedPayload, $secret);

        // Comparação segura em tempo constante
        if (!hash_equals($expectedSignature, $signatureHeader)) {
            return new WP_Error(
                'rest_forbidden',
                'Assinatura criptográfica HMAC inválida.',
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Ingestão não bloqueante e armazenamento no Redis.
     */
    public function handleIncomingWebhook(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $params = $request->get_json_params();
        if (empty($params) || !is_array($params)) {
            return new WP_Error('rest_bad_request', 'Corpo JSON inválido.', ['status' => 400]);
        }

        $eventId = $request->get_header('x-idempotency-key') ?: wp_generate_uuid4();
        $eventType = sanitize_text_field((string) ($params['event_type'] ?? 'inventory_delta'));

        try {
            $redis = new Redis();
            $redis->connect('127.0.0.1', 6379, 1.0);

            // Escrita no fluxo para processamento assíncrono
            $streamPayload = [
                'event_id' => $eventId,
                'event_type' => $eventType,
                'received_at' => (string) microtime(true),
                'payload_json' => json_encode($params, JSON_THROW_ON_ERROR),
            ];

            $messageId = $redis->xAdd('erp:stream:incoming_webhooks', '*', $streamPayload);

            return new WP_REST_Response([
                'status' => 'accepted',
                'message_id' => $messageId,
                'event_id' => $eventId,
            ], 202);
        } catch (Throwable $e) {
            return new WP_Error(
                'rest_internal_error',
                'Falha ao colocar o webhook na fila: ' . $e->getMessage(),
                ['status' => 500]
            );
        }
    }
}

#Listagem 3: Dedução atómica de inventário com SELECT FOR UPDATE (StockManager.php)

Este serviço de base de dados assegura a dedução de stock em compras no WooCommerce através de transações MySQL InnoDB, bloqueios de linha e recuperação automática de deadlocks.

<?php
declare(strict_types=1);

namespace WPPoland\ErpIntegration\Database;

use wpdb;
use RuntimeException;
use InvalidArgumentException;
use Throwable;

if (!defined('ABSPATH')) {
    exit;
}

class StockManager
{
    private wpdb $db;
    private const MAX_DEADLOCK_RETRIES = 3;

    public function __construct()
    {
        global $wpdb;
        $this->db = $wpdb;
    }

    /**
     * Deduz o inventário de forma atómica através de bloqueio exclusivo de linha.
     *
     * @param int $productId ID do produto ou variação
     * @param int $quantityToDeduct Quantidade positiva a deduzir
     * @return int Quantidade remanescente em stock
     * @throws RuntimeException Caso o stock seja insuficiente ou ocorra deadlock persistente
     */
    public function deductStockAtomically(int $productId, int $quantityToDeduct): int
    {
        if ($quantityToDeduct <= 0) {
            throw new InvalidArgumentException("A quantidade a deduzir deve ser um número positivo.");
        }

        $attempt = 0;

        while ($attempt < self::MAX_DEADLOCK_RETRIES) {
            $attempt++;

            try {
                $this->db->query('START TRANSACTION');

                // Bloquear exclusivamente o registo de stock
                $query = $this->db->prepare(
                    "SELECT meta_value FROM {$this->db->postmeta} 
                     WHERE post_id = %d AND meta_key = '_stock' 
                     FOR UPDATE",
                    $productId
                );

                $currentStockRaw = $this->db->get_var($query);

                if ($currentStockRaw === null) {
                    throw new RuntimeException("Registo de stock para o produto ID {$productId} não encontrado.");
                }

                $currentStock = (int) $currentStockRaw;

                if ($currentStock < $quantityToDeduct) {
                    $this->db->query('ROLLBACK');
                    throw new RuntimeException(
                        "Stock insuficiente. Solicitado: {$quantityToDeduct}, Disponível: {$currentStock}"
                    );
                }

                $newStock = $currentStock - $quantityToDeduct;

                // Atualizar o valor de stock
                $this->db->update(
                    $this->db->postmeta,
                    ['meta_value' => (string) $newStock],
                    ['post_id' => $productId, 'meta_key' => '_stock'],
                    ['%s'],
                    ['%d', '%s']
                );

                // Alterar o estado caso o stock chegue a zero
                if ($newStock === 0) {
                    $this->db->update(
                        $this->db->postmeta,
                        ['meta_value' => 'outofstock'],
                        ['post_id' => $productId, 'meta_key' => '_stock_status'],
                        ['%s'],
                        ['%d', '%s']
                    );
                }

                $this->db->query('COMMIT');

                // Limpeza de cache de objetos do WooCommerce após confirmação
                wp_cache_delete($productId, 'post_meta');
                if (function_exists('wc_delete_product_transients')) {
                    wc_delete_product_transients($productId);
                }

                return $newStock;
            } catch (Throwable $e) {
                $this->db->query('ROLLBACK');

                // Tratar erro de deadlock 1213 no MySQL
                $isDeadlock = str_contains($e->getMessage(), 'Deadlock found') ||
                              ($this->db->last_error && str_contains($this->db->last_error, '1213'));

                if ($isDeadlock && $attempt < self::MAX_DEADLOCK_RETRIES) {
                    // Recuo exponencial com variação aleatória antes de tentar novamente
                    $backoffUs = (int) (pow(2, $attempt) * 10000 + random_int(1000, 5000));
                    usleep($backoffUs);
                    continue;
                }

                throw new RuntimeException(
                    "Transação de base de dados para stock falhou [Tentativa {$attempt}]: " . $e->getMessage(),
                    0,
                    $e
                );
            }
        }

        throw new RuntimeException("Número máximo de repetições por deadlock excedido para o produto {$productId}.");
    }
}

#Manual de reconciliação ponto a ponto e resolução de split-brain

Mesmo utilizando filas assíncronas e chaves de idempotência, múltiplos fatores externos — tais como devoluções manuais no armazém físico, vendas presenciais em terminais POS ou o restauro de cópias de segurança da base de dados — acabarão por introduzir divergências entre o WooCommerce e o ERP.

Uma integração empresarial deve incluir mecanismos automatizados de reconciliação e regras inegociáveis de autoridade de dados.

#Regras de governação de fonte única de verdade (Single source of truth)

Para evitar conflitos nas atualizações bidirecionais, os limites de cada sistema devem estar formalmente definidos:

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                          MODELO DE GOVERNAÇÃO SPLIT-BRAIN                               │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │                               SISTEMA ERP (SISTEMA MESTRE)                            │
 │                                                                                       │
 │  - Quantidades de stock físico real (Armazéns centrais e filiais)                     │
 │  - Tabelas de preços B2B/B2C, descontos de quantidade e contratos comerciais          │
 │  - Catálogo de artigos (SKU base, códigos de barras/EAN, taxas de IVA e alfândega)    │
 │  - Faturação, contabilidade geral e contas correntes                                  │
 └───────────────────────────────────────────┬───────────────────────────────────────────┘

                       Sincronização autoritativa no sentido da loja


 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │                          WOOCOMMERCE (FRONTEND DE COMÉRCIO)                           │
 │                                                                                       │
 │  - Sessões ativas de carrinho e intenção imediata de compra                           │
 │  - Conteúdos de marketing, metadados SEO e árvores taxonómicas                        │
 │  - Reservas temporárias no checkout (TTL de 5 minutos)                                │
 │  - Contas de clientes e dados de morada de entrega                                    │
 └───────────────────────────────────────────────────────────────────────────────────────┘
  1. Quantidades de stock: O ERP é o mestre absoluto. Em caso de divergência durante a reconciliação, o stock do ERP sobrepõe-se sempre ao valor existente no WooCommerce.
  2. Preços e descontos: O ERP é a autoridade máxima. O WooCommerce calcula valores de IVA e promoções para exibição na loja, mas o ERP valida e emite os totais fiscais finais ao registar o documento de venda.
  3. Criação de encomendas: O WooCommerce é o mestre da intenção de compra. Assim que o pagamento online é confirmado, o WooCommerce guarda o registo inicial e coloca-o na fila. Após a receção e emissão de um identificador oficial pelo ERP (ERP_DOC_ID), o ERP assume a gestão dos estados posteriores (preparação, envio, anulação).

#Arquitetura de reconciliação em dois níveis

O procedimento de reconciliação completo processa-se em duas frequências complementares:

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                              CICLOS DE RECONCILIAÇÃO EM DOIS NÍVEIS                     │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │ NÍVEL 1: RECONCILIAÇÃO DELTA HORÁRIA CONTÍNUA                                         │
 │ (Frequência elevada, volume reduzido, rápida deteção de falhas)                       │
 │                                                                                       │
 │  Consulta ao ERP e WooCommerce:                                                       │
 │  WHERE updated_at >= NOW() - INTERVAL 90 MINUTE                                       │
 │  ──▶ Identificar SKUs e encomendas alteradas ──▶ Enviar para fila prioritária         │
 └───────────────────────────────────────────────────────────────────────────────────────┘

 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 │ NÍVEL 2: AUDITORIA CRIPTOGRÁFICA NOTURNA DE SOMAS DE VERIFICAÇÃO                      │
 │ (Integridade global do catálogo, comparação por blocos, correção autónoma)            │
 │                                                                                       │
 │  Catálogo WooCommerce (Bloco 01: SKUs 00001 - 00500) ──▶ SHA-256: e3b0c442...          │
 │                                                                   │                   │
 │                                                            [Comparação Hash]          │
 │                                                                   │                   │
 │  Catálogo Mestre ERP  (Bloco 01: SKUs 00001 - 00500) ──▶ SHA-256: e3b0c442...          │
 │                                                                                       │
 │  - Se os hashes COINCIDIREM: Bloco verificado. Passar ao Bloco 02.                    │
 │  - Se os hashes DIVERGIREM: Disparar sincronização detalhada apenas para o Bloco 01.  │
 └───────────────────────────────────────────────────────────────────────────────────────┘

#Nível 1: Reconciliação delta horária

A auditoria delta horária consulta as entidades modificadas nos últimos noventa minutos em ambos os sistemas (garantindo uma sobreposição de trinta minutos).

  • O WooCommerce pesquisa em wp_wc_orders os registos onde date_updated_gmt >= (NOW() - INTERVAL 90 MINUTE).
  • O adaptador do ERP recolhe as movimentações de stock e encomendas recentes.
  • O motor de reconciliação compara os pares de estados. Qualquer documento em falta ou com estado inconsistente é adicionado de imediato à fila de correção prioritária.

#Nível 2: Auditoria criptográfica noturna de somas de verificação

Comparar cem mil artigos um a um através de chamadas de API todas as noites sobrecarrega a rede e a base de dados. Para ultrapassar esta limitação, adota-se uma metodologia de hashing por blocos:

  1. O catálogo de artigos é organizado de forma lexicográfica e subdividido em blocos de 500 SKUs (por exemplo, Bloco 001: referências A0001 a A0500).
  2. É gerado um hash SHA-256 para a cadeia de texto concatenada dos artigos ordenados de cada bloco: $$\text{Hash} = \text{SHA256}\left(\sum_{i=1}^{500} \text{SKU}_i + \text{Preço}_i + \text{Stock}_i\right)$$
  3. O ERP gera somas idênticas para cada um dos blocos correspondentes.
  4. A camada de integração compara os 200 hashes de blocos obtidos.
  5. Caso 198 blocos coincidam, fica matematicamente provado que 99.000 artigos estão em total sincronia.
  6. Apenas os 2 blocos divergentes (1.000 artigos no total) são descarregados para correção cirúrgica, reduzindo o tráfego de rede em 99 por cento.

#Manual de resolução de incidentes em ambientes de produção

Sempre que ocorrerem perturbações no ambiente de produção, as equipas de desenvolvimento devem atuar com base em procedimentos estruturados.

#Incidente 1: Entregas desordenadas de webhooks e condições de corrida

  • Sintomas: Um gestor anula uma encomenda no WooCommerce. Cinco minutos mais tarde, o estado regressa a “A processar” devido ao processamento tardio de um webhook do ERP que ficou retido na rede.
  • Causa: As redes assíncronas não asseguram a entrega de pacotes na ordem original. O webhook B (emitido às 14:02) chegou antes do webhook A (emitido às 14:00).
  • Procedimento de resolução:
    1. Adicione um contador de revisão incremental (revision_id) ou uma marca temporal UTC na carga do webhook.
    2. Mantenha uma coluna de versão em wp_wc_orders_erp_lookup.
    3. Execute a atualização na base de dados apenas se a versão recebida for superior à registada:
      UPDATE wp_wc_orders_erp_lookup 
      SET erp_status = :new_status, last_event_version = :incoming_version 
      WHERE order_id = :order_id AND last_event_version < :incoming_version;
    4. Se nenhuma linha for atualizada, descarte o webhook obsoleto com o registo EVENT_SUPERSEDED.

#Incidente 2: Bloqueios mútuos (deadlocks) na base de dados durante campanhas flash

  • Sintomas: Os clientes deparam-se com erros ao concluir a encomenda durante uma grande promoção. O registo de erros do MySQL indica: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction.
  • Causa: Duas compras simultâneas adquirem os mesmos dois artigos (Artigo A e Artigo B) em ordem inversa. A Transação 1 bloqueia o Artigo A e aguarda o Artigo B; a Transação 2 bloqueia o Artigo B e aguarda o Artigo A.
  • Procedimento de resolução:
    1. Aplique uma ordenação canónica de bloqueios. Antes de invocar SELECT ... FOR UPDATE, ordene sempre os IDs dos produtos por ordem numérica crescente:
      $productIds = [842, 105, 330];
      sort($productIds, SORT_NUMERIC); // Resultado: [105, 330, 842]
      foreach ($productIds as $id) {
          $stockManager->deductStockAtomically($id, $cartItems[$id]['qty']);
      }
    2. Ao solicitar todos os bloqueios exatamente na mesma sequência, os deadlocks circulares tornam-se matematicamente impossíveis.

#Benchmarking de desempenho, monitorização e SLAs operacionais

A operação contínua de uma integração de grande escala requer a monitorização constante de telemetria para identificar quebras de desempenho de forma proativa.

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                              STACK DE MONITORIZAÇÃO DA INTEGRAÇÃO                       │
└─────────────────────────────────────────────────────────────────────────────────────────┘

 [Workers PHP WooCommerce] ──▶ Rastreios OpenTelemetry ──▶ [Jaeger / Tempo / Datadog]
 [Broker Redis Streams]     ──▶ Prometheus Exporter    ──▶ [Servidor Prometheus]
 [Daemons WP-CLI]           ──▶ Métricas StatsD        ──▶        │

                                                         [Dashboards Grafana]

                                                         [Alertas Alertmanager]

                                            ┌─────────────────────┴─────────────────────┐
                                            ▼                                           ▼
                                     [PagerDuty / Opsgenie]                      [Canal de Slack]

#Métricas essenciais e limiares de alerta

MétricaDescriçãoSLA ObjetivoLimiar de avisoLimiar crítico
erp_queue_consumer_lagNúmero de mensagens por ler em orders.incoming< 100 mensagens> 500 mensagens durante 5 min> 2.500 mensagens ou idade > 15 min
webhook_ingest_p95_msLatência do ponto de entrada (verificação até 202)< 25 milissegundos> 75 milissegundos> 250 milissegundos
order_sync_latency_p99Tempo decorrido desde a compra até ao registo no ERP< 30 segundos> 120 segundos> 600 segundos
dlq_occupancy_countVolume de mensagens com falha em erp:stream:dlq0 mensagens> 10 mensagens> 50 mensagens
db_deadlock_rateFrequência de deadlocks por 1.000 transações de compra0,00 %> 0,10 % (1 em 1.000)> 1,00 % (10 em 1.000)

#SAF-T (PT), ATCUD e a obrigação de software certificado pela AT

Portugal impõe uma restrição que altera a direção da integração antes de se escrever a primeira linha de código: o software que emite faturas tem de estar certificado pela Autoridade Tributária, e o número de certificação é impresso no próprio documento. Isto não é uma recomendação de boas práticas, é condição de legalidade do documento.

A consequência arquitetural é direta e vale a pena enunciá-la sem rodeios. O WooCommerce, na sua instalação normal, não é software certificado. Portanto a loja não emite a fatura. A loja regista a encomenda e o ERP, esse sim certificado, emite o documento fiscal e devolve os identificadores.

O documento emitido transporta três elementos que a loja tem de saber receber e mostrar:

ElementoOrigemErro comum na integração
Número de certificaçãoatribuído ao software emissoromitido no PDF apresentado ao cliente
ATCUDcódigo único por série comunicada à ATgerado na loja em vez de recebido do ERP
Código QRconstruído a partir dos dados do documentorecalculado localmente e divergente

O ATCUD merece atenção particular porque depende da comunicação prévia da série documental à AT, que devolve um código de validação. A série é um recurso registado, não um contador que a loja possa inicializar. Uma integração que tente atribuir numeração no lado do WooCommerce produz documentos que não fecham com a contabilidade e obriga a anulações.

O SAF-T (PT) fecha o ciclo. É o ficheiro que a empresa entrega à AT, gerado a partir da contabilidade e não da loja. Para que feche, cada encomenda tem de trazer de volta o identificador do documento fiscal correspondente. Se a sincronização for unidirecional, da loja para o ERP, a reconciliação mensal passa a ser trabalho manual.

Fica um alerta que compensa dar cedo ao cliente. Quando alguém pede um plugin que emita faturas diretamente no WordPress porque é mais simples, a simplificação transfere para a loja um requisito de certificação que ela não cumpre. É mais barato perder essa discussão na fase de levantamento do que na primeira inspeção.

#Próximos passos de engenharia

Construir uma integração corporativa fiável entre o WooCommerce e um sistema ERP exige uma aposta clara no desacoplamento assíncrono, na idempotência determinista, no controlo transacional de concorrência e em processos contínuos de reconciliação de dados.

Se pretende rever a sua arquitetura tecnológica atual ou projetar uma solução de integração sob medida para o seu ERP, consulte os nossos serviços de integração WooCommerce ERP ou contacte os nossos especialistas em WooCommerce na WPPoland.

Próximo passo

Transforme o artigo numa implementação real

Este bloco reforça a ligação interna e conduz o leitor para o passo seguinte mais útil dentro da arquitetura do site.

Cluster relacionado

Explorar outros serviços WordPress e base de conhecimento

Reforce o seu negócio com suporte técnico profissional em áreas-chave do ecossistema WordPress.

Por que motivo falham as chamadas síncronas REST ou SOAP entre o WooCommerce e sistemas ERP?#
As chamadas síncronas amarram a execução dos processos de trabalho PHP-FPM à latência de resposta dos servidores ERP externos. Quando o ERP sofre picos de carga, executa rotinas de manutenção ou enfrenta lentidão de rede, as threads de PHP bloqueiam até atingirem o limite de tempo do socket (normalmente 30 a 60 segundos). Isto satura o conjunto de processos do servidor web, gera erros HTTP 504 Gateway Timeout para os compradores e aborta transações sem confirmação.
De que forma o Redis Streams proporciona maior fiabilidade do que as listas normais do Redis?#
As listas convencionais com LPUSH e RPOP não oferecem acompanhamento nativo de confirmações nem gestão de estado dos consumidores. O Redis Streams disponibiliza grupos de consumidores (XREADGROUP), registos persistentes append-only, listas de mensagens pendentes (PEL), confirmações explícitas (XACK) e recuperação de mensagens órfãs após quebras de processos (XCLAIM).
Como se previne a sobrevenda (overselling) durante vendas flash de elevada concorrência?#
A prevenção da sobrevenda requer controlo transacional de concorrência. No MySQL InnoDB, dentro de uma transação ativa, executa-se SELECT stock_quantity FROM wp_wc_product_meta WHERE product_id = :id FOR UPDATE para adquirir um bloqueio de linha exclusivo antes de decrementar o stock. Em alternativa, uma atualização otimista que verifique o número da versão (UPDATE ... SET stock = stock - qty, version = version + 1 WHERE id = :id AND version = :cur AND stock >= qty) assegura uma redução atómica.
Qual é o papel da chave de idempotência no processamento de webhooks do ERP?#
A chave de idempotência (transmitida no cabeçalho X-Idempotency-Key ou calculada como um hash SHA-256 determinista do ID da encomenda e do carimbo temporal) garante que a receção repetida da mesma mensagem não provoque efeitos secundários indesejados. O recetor regista um bloqueio atómico no Redis (SETNX lock:idempotency:{key} PROCESSING EX 86400). Se a chave já estiver marcada como concluída, devolve a resposta em cache em vez de criar um documento duplicado no ERP.
Como gerir limites de pedidos (rate limits) em APIs de ERP na nuvem, como o Dynamics 365?#
As APIs na nuvem impõem quotas estritas (o Dynamics 365 Business Central limita a 600 pedidos por minuto por tenant). A integração lida com isto armazenando os pedidos em fila no Redis, aplicando um algoritmo de token bucket no consumidor e executando recuo exponencial com dispersão aleatória (full jitter) quando recebe um código HTTP 429 Too Many Requests.
O que origina fugas de memória em daemons WP-CLI com PHP 8.4 e como são solucionadas?#
O núcleo do WordPress acumula consultas à base de dados, metadados de objetos e registos de ganchos em matrizes internas de PHP que crescem continuamente em execuções prolongadas. Para resolver isto, é necessário chamar wp_cache_flush(), reiniciar a matriz $wpdb->queries, executar gc_collect_cycles() após cada lote e estabelecer um limite de memória (por exemplo, 128 MB) para que o processo termine de forma controlada e seja reiniciado pelo supervisor.
Como a reconciliação em dois níveis deteta e corrige discrepâncias silenciosas de dados?#
O modelo de reconciliação em dois níveis executa uma verificação delta horária que consulta os registos modificados nos últimos 90 minutos em ambos os sistemas. Em paralelo, uma auditoria noturna gera somas criptográficas SHA-256 de tuplos SKU-stock-preço ordenados em blocos de 500 artigos. Quando um hash não coincide, uma sincronização diferencial isola e corrige cirurgicamente apenas os registos afetados.
Como o Armazenamento de Encomendas de Alto Desempenho (HPOS) do WooCommerce beneficia a integração com o ERP?#
O HPOS migra os dados das encomendas das tabelas wp_posts e wp_postmeta para tabelas relacionais dedicadas (wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data). Isto elimina operações de junção dispendiosas, acelera a consulta de números de documentos do ERP e minimiza conflitos de bloqueios na base de dados durante importações intensivas.

Precisa de FAQ adaptado ao setor e mercado? Criamos uma versão alinhada com os seus objetivos de negócio.

Fale connosco

Artigos Relacionados

Migração WooCommerce para Merchant API

A Google desligou a Content API for Shopping a 18 de agosto de 2026 e as chamadas v2.1 devolvem agora 410 Gone. Se a sua loja WooCommerce alimenta o Merchant Center através do plugin oficial está segura, mas as integrações próprias que não passaram para a Merchant API já não sincronizam.