conector-scardua/app/v1/compras.py
Ricardo f902ccbecb Conector Scardua — recebe da API da FLS e entrega no Oracle do cliente
Servico que roda dentro da rede da Comercial Scardua e faz a ponte entre a
API publica da FLS (na VPS) e o Oracle privado (10.16.x), inalcancavel pela
internet.

Fluxo: Holmes -> API na VPS (trata e cifra) -> ESTE conector (abre e valida)
-> Oracle.

Recebimento:
- app/v1/compras.py: POST /v1/compras/dados
- app/services/cripto.py: abre o envelope AES-256-GCM + RSA-OAEP-SHA256 com a
  chave privada. O GCM autentica: corpo adulterado levanta InvalidTag em vez
  de devolver lixo
- app/schemas.py: modulo folha (so pydantic) com a config e o contrato
  PayloadCompras, que espelha o da API. Fora de sincronia devolve 422 de
  proposito, pra falhar explicito em vez de gravar dado torto

Seguranca:
- app/seguranca.py: header X-Token com compare_digest (nao vaza por tempo de
  resposta) + allowlist de IP da VPS
- .gitignore barra configs.json.*, *.bak-*, *.pem e *.key

Config:
- app/config.py e so o carregamento do configs.json
- o bloco "vps" guarda chave privada, token e ips permitidos

Estado: ainda NAO persiste. Recebe, valida e descarta (persistido: false).
O INSERT no Oracle e a proxima fase, e tem que ser MERGE por id_processo
porque o Holmes reentrega webhook.

Docs em docs/ — arquitetura, a decisao do conector (opcao A, com as
alternativas descartadas), deploy e problemas conhecidos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 06:05:03 -03:00

56 lines
2 KiB
Python

import logging
from app.config import settings
from app.schemas import Envelope, PayloadCompras
from app.seguranca import autorizar
from app.services import cripto
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import ValidationError
router = APIRouter(prefix="/compras", tags=["compras"])
# Carregada uma vez no import: parsear PEM a cada request e desperdicio.
_CHAVE = cripto.carregar_chave_privada(settings.vps.chave_privada)
@router.post("/dados", dependencies=[Depends(autorizar)])
async def receber_compras(envelope: Envelope) -> dict:
"""
Recebe o envelope cifrado da API da VPS, abre e valida.
Fase atual: so registra o que chegou. O INSERT no Oracle entra depois —
quando entrar, tem que ser MERGE por id_processo, senao reentrega de
webhook duplica linha.
"""
try:
bruto = cripto.descriptografar(envelope.model_dump(), _CHAVE)
except Exception as e:
# Chave errada, envelope adulterado ou algoritmo diferente.
logging.error(f"Falha ao abrir envelope: {type(e).__name__}: {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="envelope invalido"
)
try:
dados = PayloadCompras.model_validate(bruto)
except ValidationError as e:
# Decifrou mas o formato mudou: os dois lados sairam de sincronia.
logging.error(f"Payload fora do contrato: {e}")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="payload nao bate com o contrato esperado",
)
logging.info(
f"Compra recebida | processo={dados.id_processo} "
f"protocolo={dados.protocolo} cnpj={dados.cnpj} "
f"pedido={dados.pedido_linx} total={dados.valor_total} "
f"parcelas={len(dados.parcelas)}"
)
return {
"status": "recebido",
"id_processo": dados.id_processo,
"parcelas": len(dados.parcelas),
"persistido": False, # vira True quando o Oracle entrar
}