Skip to content

REF-301-03: Quote-to-Entitlement Pipeline

ADR: ADR-301 — Commerce: Quote, Order, Invoice, TaxInvoiceEscopo: 6 etapas do pipeline, automação vs humano em cada etapa, domain events disparados, timestamps para analytics


1. Pipeline Completo (6 etapas)

Etapa 1        Etapa 2       Etapa 3        Etapa 4       Etapa 5          Etapa 6
─────────      ─────────     ─────────      ─────────     ─────────        ─────────
Deal           Quote         Aceite         WC Order      Pagamento        Entitlement
HubSpot        Criado        Portal         Criada        Confirmado       Provisionado
   │              │             │              │              │                │
   ▼              ▼             ▼              ▼              ▼                ▼
HubSpot →    Admin revisa   Cliente vê    Automático    Stripe/Inter     Auto-provisioning
Plugin sync  e envia        e aceita      (quote→order)  webhook          por classe

2. Detalhamento por Etapa

Etapa 1: Deal HubSpot → Quote

AspectoDetalhe
TriggerDeal criado/atualizado no HubSpot
AutomaçãoHubSpot webhook sincroniza deal com middag-account
HumanoDiscussão inicial de escopo (high-touch para Services)
AlternativaAdmin cria quote manualmente na admin UI (sem HubSpot)
ResultadoQuote criado com status draft, line items do catálogo
Eventmiddag_quote_created
Timestampquote_created_at

Etapa 2: Quote Enviado ao Cliente

AspectoDetalhe
TriggerAdmin revisa quote e envia (ou auto-envio configurável)
AutomaçãoEmail com link do portal enviado ao cliente
HumanoRevisão do quote antes de enviar (opcional), precificação custom
ResultadoQuote muda para status sent
Eventmiddag_quote_sent
Timestampquote_sent_at

Etapa 3: Cliente Aceita no Portal

AspectoDetalhe
TriggerCliente visualiza quote e clica "Aceitar"
AutomaçãoStatus viewed registrado ao abrir. Aceite digital registrado.
HumanoNenhum — self-service do cliente
ResultadoQuote muda para status accepted
Eventmiddag_quote_accepted
Timestampsquote_viewed_at, quote_accepted_at

Etapa 4: WC Order Criada

AspectoDetalhe
TriggerQuote aceito dispara criação de order
Automação100% automático — OrderCreationService cria WC Order
HumanoNenhum
ResultadoWC Order criada com line items do quote, roteada para entidade correta
Eventwoocommerce_new_order (WC nativo)
Timestamporder_created_at

Etapa 5: Pagamento Confirmado

AspectoDetalhe
TriggerCliente paga via Stripe (cartão/ACH) ou Banco Inter (Pix/Boleto)
AutomaçãoWebhook woocommerce_payment_complete atualiza quote para paid
HumanoNenhum
ResultadoQuote → paid, Order → completed
Eventmiddag_quote_paid
Timestampquote_paid_at, payment_confirmed_at

Etapa 6: Entitlement Provisionado

AspectoDetalhe
Triggerwoocommerce_order_status_completed
AutomaçãoEntitlementProvisioningService analisa line items, cria Entitlement(s), provisiona downstream
HumanoNenhum
ResultadoEntitlement(s) criado(s) com status active. Quote → fulfilled.
Eventmiddag_entitlement_provisioned
Timestampentitlement_provisioned_at, quote_fulfilled_at

3. Resumo Automação vs Humano

EtapaAutomáticoHumano
1. Deal → QuoteSyncEscopo inicial (Services)
2. Quote enviadoEmailRevisão (opcional), pricing custom
3. Cliente aceita100%
4. Order criada100%
5. Pagamento100%
6. Entitlement100%

Etapas 3-6 são zero-touch. Humano envolvido apenas em escopo (Services, enterprise) e revisão de quote (opcional).


4. Domain Events

EventEtapaConsumers Principais
middag_quote_created1Sync HubSpot
middag_quote_sent2Email ao cliente
middag_quote_accepted3OrderCreationService, sync HubSpot
middag_quote_paid5EntitlementProvisioningService, sync HubSpot
middag_entitlement_provisioned6Notificação ao cliente, sync HubSpot
middag_entitlement_created6Integrações downstream

5. Timestamps para Analytics

TimestampCalculado deMétrica
quote_created_atCriação do quote
quote_sent_atEnvio ao clienteTempo de preparação
quote_viewed_atCliente abre no portalTempo de engajamento
quote_accepted_atCliente aceitaTempo até aceite (sent → accepted)
order_created_atOrder WC criadaLatência de automação
payment_confirmed_atWebhook de pagamentoTempo até pagamento
entitlement_provisioned_atEntitlement criadoLatência de provisioning
quote_fulfilled_atPipeline completoTempo total do pipeline

Métricas derivadas:

  • Tempo de conversão: quote_sent_atquote_accepted_at
  • Tempo até pagamento: quote_accepted_atpayment_confirmed_at
  • Latência do pipeline: quote_created_atentitlement_provisioned_at
  • Quotes pendentes > 7 dias: now() - quote_sent_at > 7 dias com status sent ou viewed

6. Estado refunded (9o estado)

Além dos 8 estados cobertos no pipeline principal, o quote suporta um 9o estado excepcional:

paid ──────► refunded
(estorno solicitado e processado)
AspectoDetalhe
TriggerAdmin processa estorno no WC Order
AutomaçãoWC Order refunded → Quote status atualizado automaticamente
HumanoAdmin inicia o estorno
ResultadoQuote → refunded, WC Order → refunded
Eventmiddag_quote_refunded
Timestampquote_refunded_at

Estado terminal. Sincroniza com HubSpot. Email enviado ao cliente.


7. HubSpot Webhook Handlers

O plugin recebe webhooks do HubSpot no endpoint:

POST /wp-json/middag-account/v1/webhooks/hubspot

7.1 Eventos processados

Evento HubSpotAção no plugin
quote.createdQuoteService::createFromHubSpot() — busca dados completos via HubSpot API, cria CPT, envia email
quote.updatedQuoteService::updateFromHubSpot() — atualiza campos alterados; se draftsent se publicado
quote.deletedQuoteService::deleteFromHubSpot() — soft delete (status cancelled, não exibido no portal)
deal.propertyChangeAtualiza quotes associados ao deal quando dealstage ou amount muda

7.2 Segurança de webhook

MedidaDetalhe
Assinatura HMAC SHA-256Validação via X-HubSpot-Signature-v3 e client secret da app
Timestamp validationRejeita requests com timestamp > 5 minutos (anti-replay)
Idempotênciaevent_id armazenado em cache; eventos já processados são ignorados
Rate limitingMáximo 100 webhooks/minuto por conta HubSpot

7.3 Fluxo detalhado: quote.created

1. Webhook recebido → valida assinatura e timestamp
2. Identifica conta HubSpot (middag_br ou middag_global) pelo subscription ID
3. Verifica idempotência (event_id já processado?)
4. QuoteService::createFromHubSpot(objectId, account)
   4.1. HubSpotService::getQuote() — GET /crm/v3/objects/quotes/{id}?associations=deals,line_items,companies
   4.2. Resolve Organization por hubspot_company_id_br ou _global
   4.3. Gera quote_number sequencial (formato: QT-{ANO}-{SEQUENCIAL})
   4.4. Mapeia campos HubSpot → meta: hs_title→title, hs_total_amount→total, hs_terms→terms, line items→JSON
   4.5. Persiste via QuoteRepository::create()
5. NotificationService envia email quote-sent
6. Retorna HTTP 200 ao HubSpot

7.4 Tratamento de erros

ErroComportamentoRetry
Assinatura inválidaHTTP 401, log security warningNão
Timestamp expiradoHTTP 401, log warningNão
Event ID duplicadoHTTP 200 (idempotente, sem processamento)N/A
Organization não encontradaCria quote sem org (reconciliação manual), log warningN/A
Erro na API HubSpotHTTP 500, log errorHubSpot retry automático

8. Sincronização Bidirecional (WP → HubSpot)

A sincronização WordPress → HubSpot ocorre sempre que o status de um quote muda por ação no portal.

8.1 Eventos sincronizados

Evento no pluginAção no HubSpot
Quote visualizado (sentviewed)Atualiza middag_status = viewed no quote. Timeline event no deal.
Quote aceito (viewedaccepted)Atualiza middag_status = accepted. Deal stage → "Contract Sent".
Pagamento confirmado (acceptedpaid)Atualiza middag_status = paid. Deal stage → "Closed Won". Atualiza deal amount.
Quote rejeitadoAtualiza middag_status = rejected. Nota no deal com motivo. Deal → "Closed Lost".
Quote expiradoAtualiza middag_status = expired. Timeline event. Não altera deal stage.

8.2 Propriedades customizadas no HubSpot

PropriedadeObjetoTipoValores
middag_statusQuoteEnumerationdraft, sent, viewed, accepted, paid, fulfilled, rejected, expired, refunded
middag_quote_numberQuoteStringNúmero sequencial gerado pelo plugin
middag_accepted_atQuoteDateTimeData de aceite
middag_paid_atQuoteDateTimeData de pagamento
middag_payment_methodQuoteEnumerationpix, boleto_inter, boleto_asaas, stripe_card_br, stripe_card_global
middag_wp_order_idQuoteNumberWC Order ID

8.3 Tolerância a falhas

Sincronização com HubSpot é assíncrona e tolerante a falhas. Uma falha na sincronização nunca bloqueia a operação principal:

1. Operação principal executada (ex: aceitar quote)
2. Sync disparada em background (Action Scheduler)
3. Se falhar: retry com backoff exponencial (1min, 5min, 15min, 1h, 6h)
4. Após 5 tentativas: marca como "sync_failed", notifica admin
5. Dashboard admin mostra quotes com sync pendente/falhado

9. WC Order Creation (Etapa 4 detalhada)

Quando o cliente aceita um quote, OrderCreationService::createFromQuote() executa:

9.1 Dados do WC Order

Campo WC OrderOrigem
billing_emailorg_email da Organization
billing_first_name/last_nameDados do collaborator que aceitou
billing_companyorg_name
billing_address_*Endereço da Organization
currencyquote_currency
statuspending (aguardando pagamento)

9.2 Line items

Cada item JSON do quote é convertido em WC_Order_Item_Product ou WC_Order_Item_Fee. Se o sku corresponde a um produto WC existente, o produto é vinculado. Caso contrário, cria como item avulso (fee).

9.3 Metadados associados

Meta keyValor
_quote_idID do quote (post ID)
_quote_numberNúmero legível do quote
_quote_hubspot_idID do quote no HubSpot
_middag_organizationID da Organization
_hubspot_accountmiddag_br ou middag_global

10. Gateways de Pagamento por Contexto

Conta HubSpotMoedaGatewayMétodoIdentificador
middag_brBRLBanco InterPixpix_inter
middag_brBRLBanco InterBoletoboleto_inter
middag_brBRLStripe BRCartão de créditostripe_card_br
middag_brBRLAsaas (fallback)Boletoboleto_asaas
middag_globalUSDStripe GLOBALCartão de créditostripe_card_global
middag_globalUSDStripe GLOBALACH / Wire (futuro)stripe_ach_global

10.1 Fluxo Pix (Banco Inter)

Cliente seleciona Pix → API Banco Inter gera QR code (validade: 30 min) → Cliente paga via app bancário → Webhook de confirmação → WC Order → processing → Quote → paid → Sync HubSpot.

10.2 Fluxo Boleto (Banco Inter)

Cliente seleciona Boleto → API Banco Inter gera boleto (vencimento: 3 dias úteis) → Cliente paga no banco → Webhook de confirmação (D+1) → WC Order → processing → Quote → paid → Sync HubSpot.

10.3 Fluxo Cartão (Stripe)

Cliente seleciona Cartão → Stripe Checkout ou Stripe Elements → Conta Stripe determinada pela entidade → Pagamento processado → Webhook → WC Order → processing → Quote → paid → Sync HubSpot. Parcelamento disponível apenas para BRL via Stripe BR.

10.4 Reconciliação e fallback

  • Pagamento não confirmado em 7 dias (boleto): lembrete por email.
  • Pagamento não confirmado em 15 dias (boleto): WC Order cancelado, quote retorna para viewed (novo aceite possível).
  • Pix expirado: cliente pode gerar novo QR code pelo portal enquanto o quote não expirou.

11. Email Templates

6 templates de email disparados durante o lifecycle do quote:

TriggerTemplate IDAssuntoDestinatário
Quote enviado ao clientequote-sent"Novo orçamento #{number} disponível"org_email
Quote aceito pelo clientequote-accepted"Orçamento #{number} aceito"Equipe de vendas + org_email
Quote rejeitado pelo clientequote-rejected"Orçamento #{number} recusado"Equipe de vendas
Quote próximo de expirarquote-expiring"Seu orçamento #{number} expira em {days} dias"org_email
Quote expiradoquote-expired"Orçamento #{number} expirou"org_email
Pagamento confirmadoquote-paid"Pagamento confirmado — Orçamento #{number}"org_email + Equipe de vendas

Lembrete pré-expiração: Cron diário envia quote-expiring quando valid_until - hoje <= 3 dias e status IN (sent, viewed). Máximo 1 lembrete por quote.


12. Cron Job de Expiração

Frequência: diária (01:00 UTC)
Hook: middag_check_expired_quotes
Lógica:
  1. SELECT quotes WHERE status IN ('sent', 'viewed') AND valid_until < CURDATE()
  2. Para cada quote:
     a. Atualizar status para 'expired'
     b. Enviar email quote-expired para cliente
     c. Sincronizar status com HubSpot
  3. Registrar log com quantidade de quotes expirados

13. Transições Bloqueadas

DeParaMotivo
draftacceptedQuote precisa ser enviado antes de ser aceito
expiredacceptedExpirado não pode ser aceito — vendedor deve criar novo quote
rejectedacceptedRejeitado não pode ser aceito — vendedor deve criar novo quote
paidacceptedRetrocesso de status não permitido
fulfilledqualquerEstado terminal — nenhuma transição possível
refundedqualquerEstado terminal — nenhuma transição possível