de3898878a
DESIGN_ASAAS_GATEWAY.md documenta arquitetura. Schema novo: 2 migrations (tables + RLS) cobrindo asaas_customers + asaas_payments + asaas_webhook_events. Client service asaasGatewayService.js no features/financeiro. 3 Edge Function stubs (create-payment-record, cancel-payment, sync-payment) — webhook financial_records eh Fase B. Bloqueadores Fase B (implementacao real): user precisa criar conta Asaas, gerar API keys, configurar webhook, setar ENV vars no Supabase. Decisao modelo de negocio (A/B/C) tambem pendente. Stops marcados claramente no DESIGN. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
139 lines
7.2 KiB
PL/PgSQL
139 lines
7.2 KiB
PL/PgSQL
-- ============================================================================
|
|
-- Asaas Gateway — Tier 1 (cobrança de paciente) — schema foundation
|
|
-- ----------------------------------------------------------------------------
|
|
-- Cria 3 tabelas novas + adiciona 4 colunas em payment_settings.
|
|
-- Schema preparado pra Fase 3 do ROADMAP (gateway de pagamento).
|
|
--
|
|
-- ⚠️ Não habilita o gateway sozinho. Requer:
|
|
-- - Edge Functions deployadas
|
|
-- - API keys configuradas em payment_settings
|
|
-- - Webhook setado no dashboard Asaas
|
|
--
|
|
-- Ver: development/02-auditoria/DESIGN_ASAAS_GATEWAY.md
|
|
-- ============================================================================
|
|
|
|
BEGIN;
|
|
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
-- 1. asaas_customers — mapping patient ↔ Asaas customer
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS public.asaas_customers (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid NOT NULL,
|
|
patient_id uuid NOT NULL REFERENCES public.patients(id) ON DELETE CASCADE,
|
|
asaas_customer_id text NOT NULL,
|
|
environment text NOT NULL DEFAULT 'sandbox' CHECK (environment IN ('sandbox', 'prod')),
|
|
-- dados cacheados (sincronizados quando atualizar patient)
|
|
name text NOT NULL,
|
|
email text,
|
|
cpf_cnpj text,
|
|
phone text,
|
|
address jsonb,
|
|
created_at timestamptz DEFAULT now() NOT NULL,
|
|
updated_at timestamptz DEFAULT now() NOT NULL,
|
|
deleted_at timestamptz,
|
|
CONSTRAINT asaas_customers_unique_per_env UNIQUE (tenant_id, patient_id, environment)
|
|
);
|
|
|
|
COMMENT ON TABLE public.asaas_customers IS
|
|
'Mapping de pacientes para Asaas customers (1:1 por environment). Cacheado pra evitar re-criação a cada cobrança.';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asaas_customers_lookup
|
|
ON public.asaas_customers (tenant_id, patient_id)
|
|
WHERE deleted_at IS NULL;
|
|
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
-- 2. asaas_payments — 1 row por cobrança gerada
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS public.asaas_payments (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid NOT NULL,
|
|
financial_record_id uuid NOT NULL REFERENCES public.financial_records(id) ON DELETE CASCADE,
|
|
asaas_customer_id uuid REFERENCES public.asaas_customers(id),
|
|
asaas_payment_id text NOT NULL,
|
|
asaas_invoice_id text,
|
|
environment text NOT NULL DEFAULT 'sandbox' CHECK (environment IN ('sandbox', 'prod')),
|
|
billing_type text NOT NULL CHECK (billing_type IN ('PIX', 'BOLETO', 'CREDIT_CARD', 'UNDEFINED')),
|
|
status text NOT NULL,
|
|
value numeric(10, 2) NOT NULL,
|
|
net_value numeric(10, 2),
|
|
due_date date NOT NULL,
|
|
payment_date timestamptz,
|
|
invoice_url text,
|
|
payment_url text,
|
|
bank_slip_url text,
|
|
pix_qr_code text,
|
|
pix_copy_paste text,
|
|
created_at timestamptz DEFAULT now() NOT NULL,
|
|
updated_at timestamptz DEFAULT now() NOT NULL,
|
|
cancelled_at timestamptz,
|
|
CONSTRAINT asaas_payments_unique_per_env UNIQUE (asaas_payment_id, environment)
|
|
);
|
|
|
|
COMMENT ON TABLE public.asaas_payments IS
|
|
'Cobranças geradas no Asaas. Status raw do Asaas; mapeamento pra financial_records.status acontece no JS.';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asaas_payments_record
|
|
ON public.asaas_payments (financial_record_id);
|
|
CREATE INDEX IF NOT EXISTS idx_asaas_payments_lookup
|
|
ON public.asaas_payments (tenant_id, status, due_date)
|
|
WHERE cancelled_at IS NULL;
|
|
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
-- 3. asaas_webhook_events — idempotência + audit
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS public.asaas_webhook_events (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
event_id text,
|
|
event_type text NOT NULL,
|
|
asaas_payment_id text,
|
|
payload jsonb NOT NULL,
|
|
processed_at timestamptz,
|
|
processing_error text,
|
|
received_at timestamptz DEFAULT now() NOT NULL
|
|
);
|
|
|
|
COMMENT ON TABLE public.asaas_webhook_events IS
|
|
'Audit + idempotência de webhooks Asaas. event_id usado pra dedupe (Asaas faz retry).';
|
|
|
|
-- event_id UNIQUE quando preenchido (Asaas nem sempre manda)
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_asaas_webhook_events_event_id
|
|
ON public.asaas_webhook_events (event_id)
|
|
WHERE event_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asaas_webhook_events_payment
|
|
ON public.asaas_webhook_events (asaas_payment_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asaas_webhook_events_unprocessed
|
|
ON public.asaas_webhook_events (received_at)
|
|
WHERE processed_at IS NULL;
|
|
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
-- 4. payment_settings — colunas pra config Asaas por tenant
|
|
-- ──────────────────────────────────────────────────────────────────────────
|
|
-- API keys plaintext nesta migration. Em produção, mover pra pgsodium/Vault.
|
|
|
|
ALTER TABLE public.payment_settings
|
|
ADD COLUMN IF NOT EXISTS asaas_api_key_sandbox text,
|
|
ADD COLUMN IF NOT EXISTS asaas_api_key_prod text,
|
|
ADD COLUMN IF NOT EXISTS asaas_environment text DEFAULT 'sandbox'
|
|
CHECK (asaas_environment IN ('sandbox', 'prod')),
|
|
ADD COLUMN IF NOT EXISTS asaas_webhook_token text,
|
|
ADD COLUMN IF NOT EXISTS asaas_enabled boolean DEFAULT false NOT NULL;
|
|
|
|
COMMENT ON COLUMN public.payment_settings.asaas_api_key_sandbox IS
|
|
'API key Asaas SANDBOX. PLAINTEXT por enquanto — migrar pra Vault em prod.';
|
|
COMMENT ON COLUMN public.payment_settings.asaas_api_key_prod IS
|
|
'API key Asaas PRODUÇÃO. PLAINTEXT por enquanto — migrar pra Vault em prod.';
|
|
COMMENT ON COLUMN public.payment_settings.asaas_environment IS
|
|
'Qual key usar: sandbox (testes) ou prod (real). Default sandbox por segurança.';
|
|
COMMENT ON COLUMN public.payment_settings.asaas_webhook_token IS
|
|
'Token customizado pra webhook receiver validar. Setar mesmo valor no dashboard Asaas.';
|
|
COMMENT ON COLUMN public.payment_settings.asaas_enabled IS
|
|
'Flag que controla se gateway Asaas está habilitado pro tenant. Default false (opt-in).';
|
|
|
|
COMMIT;
|