Por que a versão, e não o documento
Um registro dizendo “a Ana aceitou os seus termos” não vale nada no dia em que os termos mudam. Ele prova que alguém marcou uma caixinha ao lado do que o texto diz hoje — que é o contrário do motivo pelo qual o registro é guardado. Então as palavras moram numa versão, e publicar congela. O corpo, o resumo, a data de vigência e a regra de reaceite viram imutáveis — garantido por um trigger no banco, não por convenção — e corrigir uma vírgula é publicar outra versão, do mesmo jeito que remarcar um preço é escrever um preço novo. O aceite guarda o id da versão e o número dela, junto com o momento, o endereço de onde veio o clique e o navegador em que foi feito.Publicando
O documento é criado sem texto nenhum: um nome, um endereço (slug) e a regra
sobre o aceite ser obrigatório. Criar um nunca coloca nada na frente de ninguém.
effective_at pode estar no futuro, que é o que um aviso prévio é: até essa
data a versão anterior continua em vigor, e continua sendo a oferecida para aceite.
Não pode estar no passado — datar termos para trás seria afirmar que alguém foi
regido por palavras que não existiam.
Publicar acontece exatamente uma vez por versão. A segunda chamada responde 404,
porque um segundo publish moveria a data de vigência de termos que gente já
aceitou.
Avisando
notify: true no publish coloca uma notificação action no sino de todo mundo
que a publicação põe diante de um checkbox — e só deles, calculado com o mesmo
piso que o card usa. Vem desligado, porque uma versão publicada para consertar
um título não pode virar cobrança na notificação de ninguém.
Com effective_at no futuro, o aviso espera a data. Ele sai no dia em que os
termos passam a valer, para quem devê-los naquele dia. Avisar na publicação
alcançaria quem deve a versão anterior, sobre palavras que ainda não regem
ninguém — e o dia da vigência passaria sem acontecer nada. Uma varredura noturna
envia, então o aviso chega dentro de um dia da vigência, não no segundo dela.
Quem é perguntado de novo, e quem não é
requires_reacceptance é o campo de maior alcance aqui, e o padrão é true.
Cada documento tem um piso: a maior versão em vigor que declarou mudança
material. A pessoa está em dia quando aceitou qualquer versão igual ou acima dele.
- Publique um link corrigido com
requires_reacceptance: false— ninguém é perguntado de novo, e todo aceite já registrado continua valendo. - Publique uma cláusula de arbitragem com
requires_reacceptance: true— todo mundo abaixo do piso volta a ficar pendente, na mesma instrução, sem nada para reprocessar.
true por causa do que cada erro custa. Perguntar de novo sem precisar
custa um clique. Não perguntar quando devia é uma mudança de contrato que ninguém
foi avisado — que é exatamente o que este módulo existe para impedir.
O que required significa, e o que não significa
enforcement: "required" diz que ninguém usa o produto aqui sem ter aceitado. O
UserKit não é dono de nenhuma porta em que uma conta nasce — o seu produto é —,
então ele nunca recusa ninguém. O que ele faz é avisar: o aceite é
blocking em vez de só oferecido, e toda leitura diz isso. GET /v1/contact/me carrega
pending_agreements, GET /v1/contact/legal carrega o quadro inteiro, e cada
pendência diz blocking: true. Nada recusa uma chamada comum por causa de um
aceite pendente, e POST /v1/contact/token em particular nunca recusa.
Essa última é a razão de toda a posição. O token é o fluxo de refresh: barrar ali
significaria que publicar termos novos desloga todos os seus clientes do seu
produto, de uma vez, por causa de uma caixinha. Publicar termos tem que significar
“todo mundo é perguntado”, nunca “todo mundo é expulso” — então o que fazer com
uma pendência é decisão do seu produto, tomada onde dá para ver o que interromper
custa.
Lendo os documentos
GET /v1/legal/{publishable_key} é público e cacheável, endereçado pela chave que
já está na sua página. E tem que ser: o seu formulário de cadastro desenha o link
dos termos antes de existir qualquer sessão, e um formulário que não consegue mostrar o
que precisa mostrar é um formulário que não pode aceitar cadastro.
innerHTML: é prosa vinda de um banco desenhando na sua página.
Perguntando, dentro do seu produto
A caixinha do cadastro fica no seu próprio formulário, e o seu backend registra o aceite (abaixo). Depois disso, o<Agreements /> desenha o que está pendente e
nada no caso comum, que é o que o torna seguro de montar de forma permanente:
position="center" ele é um modal, e aí entra numa fila: o centro da página
comporta um diálogo por vez, e este é o primeiro dela — a condição que você
declarou vem antes de uma novidade publicada e de uma pesquisa, que esperam a vez. A
ordem, e como trocá-la, estão em Pesquisas.
Num app Next, importe do @userkit/nextjs — é o mesmo componente, e em
modo proxy tanto a leitura quanto o aceite são
encaminhados pelos handlers, com a sessão num cookie httpOnly da sua própria origem. O
useLegalDocuments atravessa junto e continua sem exigir sessão: a rota pública dos
documentos está na allowlist sem authenticated, porque o seu formulário de
cadastro precisa desenhar o link antes de existir sessão.
Ele lê a lista de pendências do estado da sessão em vez de ficar consultando, então
uma versão publicada hoje de manhã aparece sem requisição própria. Documentos
opcionais ficam de fora a menos que você peça (includeOptional) — um documento
opcional é uma oferta, e interromper alguém com um faria a palavra não significar
nada.
Dirigindo na mão são duas chamadas:
200 e recorded diz
qual delas gravou a linha; o horário e a origem continuam sendo os do primeiro
aceite. Uma versão que saiu de vigor responde 404, que é como uma página deixada
aberta a noite toda falha — releia em vez de tentar de novo.
Registrando o aceite pelo seu backend
O formulário de cadastro é seu, então a caixinha é marcada no seu app e o seu backend nos conta:source: api, para sempre. São os aceites cuja evidência
é sua e não nossa, e a pergunta “como você sabe que essa pessoa aceitou” tem outra
resposta para eles.
O registro
GET /v1/organization/legal/{id}/acceptances é a lista de evidência: quem aceitou,
qual versão, quando, de onde e por qual porta.
Um aceite sobrevive ao apagamento da pessoa que ele nomeia. Isso é
proposital: ele é o seu registro da base legal sobre a qual você tratou alguém, e
uma pessoa exercendo o direito ao apagamento não desfaz com isso o aceite dos
termos sob os quais foi atendida. O que o apagamento leva é o par que é sobre a
pessoa e não sobre o acordo — o endereço e o navegador —, deixando ids, um número
de versão e um momento. A lista mostra contact: null nessas linhas, que é a cara
que o registro deve ter depois.
Pelo mesmo motivo, um documento que já publicou alguma coisa não pode ser
excluído. Tirar termos do ar é PATCH { "archived": true }: ele sai do documento
público e deixa de ser pedido a qualquer pessoa, e todos os aceites continuam lá.
Eventos
legal_document.published dispara uma vez por versão e é entregável nos seus
webhooks — invalide a página de termos cacheada no seu site, mande o aviso, reabra
o portão dentro do seu produto. legal_agreement.accepted dispara no primeiro
aceite de uma versão por uma pessoa, que é o momento em que um portão em outro
lugar pode abrir.
Os fatos do rascunho (legal_document.updated) ficam dentro: uma versão não
publicada é uma posição jurídica que você ainda não tomou, e entregabilidade é uma
porta de mão única.