Skip to main content
Um programa de indicação são três fatos e uma regra que atravessa todos eles. Um código pertence a um dos seus clientes e carrega quanto ele paga. Uma atribuição diz que um contato entrou por um código — uma vez, para sempre. Uma comissão é uma linha num livro-razão, escrita quando uma fatura é paga e escrita de novo, negativa, quando ela é estornada. A regra é o primeiro toque: quem indicou primeiro indicou. Um segundo ?ref= nunca reaponta ninguém, porque a comissão já foi ganha pelo primeiro. O painel desenha isso em Receita → Indicações, atrás de billing:read e billing:write.
Nada aqui paga ninguém. Isto registra o que é devido; a transferência é um ato seu, no seu próprio gateway de pagamento, sob os seus termos. Toda resposta diz isso com payouts_are_manual: true, e as telas repetem ao lado do número — um total que não dissesse seria lido como um total que já foi pago.

Emitir um código

Um código pertence a um contato — a pessoa que será paga pela apresentação — e carrega os seus termos. Não existe um padrão por ambiente para cair de volta: um código sem termos é um pagamento que ninguém combinou.
Exatamente um dos dois termos: O ambiente viaja como ?environment=live ou ?environment=test também nas escritas, e não é opcional — um código emitido sem ambiente teria que assumir um, e assumir o live é como um código feito para um teste começa a atribuir cadastros de verdade. Desligar e religar um código são duas rotas em vez de um campo, porque são atos opostos:
Um código inativo não atribui mais ninguém. O que ele já trouxe continua rendendo: a atribuição era um fato sobre o passado, e desativar é uma afirmação sobre o futuro.

Capturar o clique

Não existe endpoint para isso. O ?ref=CODE é lido da página de entrada pelo SDK, guardado junto com o resto da atribuição de primeiro toque, e viaja com a primeira chamada que identifica alguém — um boot com external_id, ou um magic link resgatado.
Essa é a integração inteira, e é por isso que funciona para quem clica na segunda-feira e se cadastra na quinta: o valor fica guardado no navegador e sobrevive aos dias entre o clique e a conta. Um endpoint dedicado seria um segundo caminho para o mesmo estado — e o que esquece as regras abaixo.
O código é capturado junto com o resto do primeiro toque, nunca sozinho. Se você tirar o ?ref= da URL antes de o SDK rodar, ou se a sua landing page redirecionar sem levar a query string, nada é capturado e não há segunda chance: o primeiro toque aconteceu, e registrou nada.

O que não atribui nada, e nunca derruba um cadastro

Cada um destes não escreve atribuição alguma e não recusa nada — o cadastro que carregou o código dá certo do mesmo jeito:
  • Um código que não existe neste ambiente: um erro de digitação, ou um link montado à mão contra o outro ambiente.
  • Um código inativo.
  • O próprio código de quem está entrando. A conta que paga seria a conta que recebe, o que é um desconto se passando por indicação.
  • Um segundo ?ref= para alguém já atribuído. O primeiro toque permanece.
  • Um visitante anônimo. Atribuição precisa de um contato identificado; um dispositivo que nunca entrou é uma linha que ninguém conseguiria achar depois.
  • Um boot federado sem prova para um contato que já existe. Um boot sem hash é uma afirmação que qualquer pessoa que saiba um external_id consegue fazer do navegador, então deixar uma delas grudar uma indicação em alguém que já existe seria uma comissão roubada com um parâmetro de URL. Um boot que cria o contato pode atribuir — a mesma regra que impede uma chamada sem prova de reescrever um name ou um email.

O que um pagamento rende

Quando uma fatura de um cliente indicado é paga, uma linha de comissão é escrita. Ela sai do que de fato continuou pago — o total da fatura menos tudo que foi estornado — então um estorno não é outro caminho, é a mesma conta sobre um número menor. Para uma porcentagem: líquido × rate_basis_points ÷ 10000, truncado, então você nunca paga uma fração de centavo acima dos termos. Para um valor fixo, a comissão é proporcional ao que continuou pago, e essa é a única decisão aritmética aqui que vale dizer em voz alta. Tudo-ou-nada faria um estorno de um centavo apagar um prêmio de R$ 50; “mantém o prêmio inteiro a menos que a fatura seja estornada por completo” pagaria integralmente num estorno de 99%. Proporcional é a única regra que responde aos dois. Um estorno maior que a fatura — um crédito de cortesia acima do valor — devolve a comissão e nada além disso. Um saldo negativo seria um afiliado devendo dinheiro a você por uma venda que ele trouxe.
Um código de valor fixo diante de uma fatura em outra moeda não registra nada e é logado: nenhuma taxa de câmbio é inventada num pagamento a afiliado. Ou os termos ou o preço estão errados, e qual dos dois é uma decisão que só você pode tomar.

Ler o que você deve

Saldos primeiro, as linhas por trás deles depois, numa resposta só — o relatório é uma tela que alguém abre para decidir quem pagar este mês, e as linhas são a evidência do número ao lado de cada nome.
Os saldos são por código e por moeda, e nunca somados entre moedas — dois preços em duas moedas são dois valores que alguém combinou, não conversões um do outro. O kind de cada linha é earned ou reversed, e basis_minor é o valor sobre o qual a comissão foi calculada. entries é paginado com limit (50 por padrão, 200 no máximo) e offset; balances não é, porque é uma linha por código e moeda. O livro-razão é append-only: uma correção é uma linha nova, nunca uma edição, e o banco recusa um UPDATE na cara. É também por isso que o saldo é uma dobra sobre as linhas, e não um total guardado que poderia discordar delas.

O que quem indicou vê

O seu cliente lê o próprio lado em GET /v1/contact/referrals, com a sessão de contato dele — os códigos que tem, quantas pessoas cada um trouxe e quanto cada um rendeu. O portal hospedado desenha isso como Indique. Essa rota exige uma sessão de contato verificada, ao contrário da maior parte de /v1/contact/*. A resposta é dinheiro devido a quem está segurando a sessão, então uma sessão identificada que não provou nada deixaria qualquer pessoa que saiba um external_id ler os ganhos de outra pelo navegador. Uma sessão anônima passa e lê uma lista vazia, o que é verdade e não uma recusa: um visitante não tem código nenhum.
Ela nunca diz quem foi indicado. Isso é uma lista de contas de outras pessoas, e quem segura o código não tem direito sobre ela — a contagem é a resposta que essa pessoa merece. Também não existe rota para um contato emitir o próprio código. Um que qualquer um emite para si mesmo é um que um atacante emite em looping; os códigos são emitidos por você.

No seu app

<Referrals /> desenha a mesma tela dentro do seu produto, e client.getReferrals() entrega a resposta crua para quem quer montar a própria:
Num app Next, importe do @userkit/nextjs — é o mesmo componente, e em modo proxy a leitura é encaminhada pelos handlers, com a sessão num cookie httpOnly da sua própria origem. Ela não leva X-Customer-Id: a comissão é do contato, e trocar de time não a move. O componente carrega as três recusas da rota: não lista quem foi indicado, não oferece emitir código, e mostra payouts_are_manual como frase em vez de um botão que nada atrás dele honraria. Ele não desenha nada para quem não tem código — a maioria das pessoas nunca vai ter um, e uma caixa explicando isso para todas elas é ruído. empty é a frase da sua página para o caso contrário: quem ganha um código, e como, é política sua, e nenhum dicionário aqui adivinha.

O que sobrevive a quê

Duas perguntas aparecem depois que o programa já rodou um tempo, e as respostas são deliberadas em vez de acidentais:
  • Uma fusão. Quando dois contatos são fundidos e os dois carregam uma atribuição, a mais antiga sobrevive — primeiro toque aplicado por comparação, porque só uma atribuição pode viver no contato de destino.
  • Um apagamento. As linhas de comissão sobrevivem ao contato. Dinheiro devido a quem fez a apresentação não deixa de ser devido quando a pessoa apresentada encerra a conta, então as linhas guardam os ids por valor e nenhuma chave estrangeira as arrasta junto.