Files
agenciapsilmno/Nova-Dev-Doc/Billing → Subscription → Entitlements/documentacao-billing-completa-agencia-psi.html
Leonardo f733db8436 ZERADO
2026-03-06 06:37:13 -03:00

309 lines
6.6 KiB
HTML

<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8"/>
<title>Arquitetura Técnica Completa — Billing & Assinaturas | Agência PSI</title>
<style>
body{font-family:Arial,Helvetica,sans-serif;margin:40px;line-height:1.65;color:#111}
h1,h2,h3{margin-top:36px}
code,pre{background:#f4f4f4;padding:12px;border-radius:8px;display:block;overflow:auto;font-size:13px}
.section{margin-bottom:40px}
.small{font-size:13px;color:#555}
ul{margin-left:20px}
.diagram{background:#fafafa;border:1px solid #ddd;padding:16px;border-radius:8px;font-family:monospace;font-size:13px}
</style>
</head>
<body>
<h1>Arquitetura Técnica Completa — Billing & Assinaturas</h1>
<p class="small">Projeto: Agência PSI • Documento estrutural definitivo do domínio de Billing.</p>
<div class="section">
<h2>1. Visão Arquitetural Geral</h2>
<div class="diagram">
USUÁRIO
subscription_intents (VIEW unificada)
▼ (RPC activate_subscription_from_intent)
subscriptions
subscription_events (auditoria)
entitlements (derivados do plano)
</div>
<p>Separação estrutural:</p>
<ul>
<li><strong>plans</strong> → catálogo</li>
<li><strong>plan_prices</strong> → preço versionado</li>
<li><strong>subscription_intents_*</strong> → intenção pré-pagamento</li>
<li><strong>subscriptions</strong> → assinatura ativa</li>
<li><strong>subscription_events</strong> → histórico</li>
</ul>
</div>
<div class="section">
<h2>2. Estados Oficiais da Assinatura</h2>
<pre>
pending
active
past_due
suspended
cancelled
expired
</pre>
</div>
<div class="section">
<h2>3. Índice de Integridade de Preços</h2>
<pre>
create unique index if not exists uq_plan_price_active
on plan_prices (plan_id, interval, currency)
where is_active = true and active_to is null;
</pre>
</div>
<div class="section">
<h2>4. Função Completa — activate_subscription_from_intent</h2>
<pre>
CREATE OR REPLACE FUNCTION public.activate_subscription_from_intent(p_intent_id uuid)
RETURNS subscriptions
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
declare
v_intent record;
v_sub public.subscriptions;
v_days int;
v_user_id uuid;
v_plan_id uuid;
v_target text;
begin
select * into v_intent
from public.subscription_intents
where id = p_intent_id;
if not found then
raise exception 'Intent não encontrada';
end if;
if v_intent.status <> 'paid' then
raise exception 'Intent precisa estar paid';
end if;
select p.id, p.target
into v_plan_id, v_target
from public.plans p
where p.key = v_intent.plan_key
limit 1;
if v_plan_id is null then
raise exception 'Plano não encontrado';
end if;
v_target := lower(coalesce(v_target,''));
if v_target = 'clinic' and v_intent.tenant_id is null then
raise exception 'Intent clinic exige tenant_id';
end if;
if v_target = 'therapist' and v_intent.tenant_id is not null then
raise exception 'Intent therapist não deve ter tenant_id';
end if;
v_days := case when v_intent.interval = 'year' then 365 else 30 end;
v_user_id := coalesce(v_intent.created_by_user_id, v_intent.user_id);
if v_user_id is null then
raise exception 'user_id obrigatório';
end if;
if v_target = 'clinic' then
update subscriptions
set status = 'cancelled',
cancelled_at = now()
where tenant_id = v_intent.tenant_id
and status = 'active';
else
update subscriptions
set status = 'cancelled',
cancelled_at = now()
where user_id = v_user_id
and tenant_id is null
and status = 'active';
end if;
insert into subscriptions (
user_id,
plan_id,
status,
current_period_start,
current_period_end,
tenant_id,
plan_key,
interval,
provider,
started_at,
activated_at
)
values (
v_user_id,
v_plan_id,
'active',
now(),
now() + make_interval(days => v_days),
case when v_target='clinic' then v_intent.tenant_id else null end,
v_intent.plan_key,
v_intent.interval,
'manual',
now(),
now()
)
returning * into v_sub;
return v_sub;
end;
$function$;
</pre>
</div>
<div class="section">
<h2>5. Função Completa — transition_subscription (segura)</h2>
<pre>
create or replace function public.transition_subscription(
p_subscription_id uuid,
p_to_status text,
p_reason text default null,
p_metadata jsonb default null
)
returns subscriptions
language plpgsql
security definer
as $$
declare
v_sub subscriptions;
v_uid uuid;
v_allowed boolean := false;
begin
v_uid := auth.uid();
select * into v_sub
from subscriptions
where id = p_subscription_id;
if not found then
raise exception 'Assinatura não encontrada';
end if;
if is_saas_admin() then
v_allowed := true;
end if;
if not v_allowed
and v_sub.tenant_id is null
and v_sub.user_id = v_uid then
v_allowed := true;
end if;
if not v_allowed
and v_sub.tenant_id is not null then
if exists (
select 1 from tenant_members tm
where tm.tenant_id = v_sub.tenant_id
and tm.user_id = v_uid
and tm.status = 'active'
and tm.role = 'tenant_admin'
) then
v_allowed := true;
end if;
end if;
if not v_allowed then
raise exception 'Sem permissão';
end if;
update subscriptions
set status = p_to_status,
updated_at = now()
where id = p_subscription_id
returning * into v_sub;
insert into subscription_events (
subscription_id,
owner_id,
event_type,
created_at,
created_by,
source,
reason,
metadata
)
values (
v_sub.id,
coalesce(v_sub.tenant_id, v_sub.user_id),
'status_changed',
now(),
v_uid,
'manual_transition',
p_reason,
p_metadata
);
return v_sub;
end;
$$;
</pre>
</div>
<div class="section">
<h2>6. Máquina de Estados Recomendada</h2>
<div class="diagram">
pending → active → past_due → suspended → cancelled
expired
</div>
<p>Recomendação futura: validar allowed_transitions em tabela dedicada.</p>
</div>
<div class="section">
<h2>7. Checklist de Validação Estrutural</h2>
<ul>
<li>Intent paga gera subscription ativa</li>
<li>subscription_id vinculado corretamente</li>
<li>Cancelamento gera evento</li>
<li>Reativação preserva histórico</li>
<li>Tenant isolation validado</li>
</ul>
</div>
<div class="section">
<h2>8. Roadmap Estrutural Futuro</h2>
<ul>
<li>State machine formal com allowed_transitions</li>
<li>Automação de expiração por cron</li>
<li>Integração Stripe mantendo arquitetura</li>
<li>Health monitor automatizado</li>
</ul>
</div>
<hr/>
<p class="small">Documento técnico estrutural consolidado após implementação real validada.</p>
</body>
</html>