Assinaturas como fonte de verdade
Como a área de conta de uma loja headless foi construída direto sobre o modelo de assinaturas do Recharge: listar, editar, cancelar, reativar e recomprar, sem banco de dados no meio. Com o roteiro de montagem no fim.
01Por que o centro é a assinatura e não o pedido
Na maioria das lojas headless, a área de conta gira em torno do pedido: uma lista do que já foi comprado, com rastreio e recibo. Para um negócio de assinatura recorrente com produto personalizado, isso resolve pouca coisa. O cliente entra na conta para mexer no que ainda vai acontecer: adiar a entrega do mês que vem, trocar um produto, mudar a fragrância.
Então a assinatura do Recharge virou o objeto central do projeto. A Shopify continua dona do catálogo, da autenticação e do checkout, mas quem responde o que esse cliente tem contratado hoje é o Recharge. A interface inteira é uma leitura desse modelo, e toda ação do cliente termina numa escrita nele. Não tem banco intermediário nem cópia local do estado.
02Três fontes de dados, um loader
A aplicação roda em Hydrogen com React Router 7, hospedada no Oxygen, que na prática é um Cloudflare Worker. Quem fala com o mundo externo é o loader da rota de conta: ele pega o e-mail do cliente autenticado, resolve o ID correspondente no Recharge e busca assinaturas, cobranças agendadas, métodos de pagamento e endereços. Em paralelo saem as queries da Shopify.
O resultado desce inteiro pelo contexto do Outlet. As telas de assinaturas, edição, reativação e recompra não buscam estado por conta própria: todas leem a mesma hidratação. Isso deixa o loader mais pesado, mas evita quatro telas exibindo versões diferentes da mesma informação.
O token administrativo do Recharge fica no worker e não sai de lá. O browser só conversa com rotas da própria aplicação.
03Uma assinatura por item de linha
O Recharge cria uma assinatura para cada item. Um cliente com shampoo, condicionador e leave-in na mesma entrega tem três assinaturas, cada uma com seu ciclo e sua data de cobrança. Só que ninguém pensa no próprio pedido assim: o cliente pensa na caixa que chega dia 14.
Juntar as três de volta num card é o trabalho de modelagem mais importante da tela, e a chave tem duas partes:
Quando não existe cobrança agendada, no caso de uma assinatura pausada por exemplo, caímos num fallback por endereço mais data agendada. É como o próprio Recharge agrupa uma entrega.
04Salvar uma edição escreve três listas
Na tela de edição o cliente faz coisas que parecem simples: troca um produto, adiciona outro, muda a frequência, tira um item. No modelo, quase nenhuma delas é uma operação só. Trocar produto é criar uma assinatura e cancelar outra. Mudar data e frequência é atualizar todas as assinaturas daquela entrega ao mesmo tempo, senão a caixa se parte em duas.
A tela não dispara nada enquanto o cliente mexe. Ela acumula as intenções e manda um envelope só quando ele salva:
POST /api/subscriptionService
{
"subsToCreate": [ /* produtos adicionados ou substitutos */ ],
"subsToUpdate": [ /* data, frequência, quantidade */ ],
"subsToCancel": [ /* removidos e trocados */ ]
}
Esse envelope vai para um serviço de escrita dedicado, autenticado com um JWT de vida curta. Como o runtime é um Worker, as bibliotecas de JWT feitas para Node não servem: o token é assinado com crypto.subtle (HMAC-SHA256) da Web Crypto API e guardado em memória até perto de vencer.
A vantagem de concentrar a escrita num envelope só aparece com o tempo. A ordem das operações e o agrupamento delas ficam num lugar; o componente de tela descreve o que o cliente quis e não precisa orquestrar a sequência.
05Regras que mudam sem deploy
Algumas regras mudam com frequência e não deveriam depender de deploy: até quando o cliente pode empurrar a próxima entrega dada a frequência dele, quais motivos de cancelamento a loja quer oferecer. Elas ficam em metafields da loja, editáveis no admin da Shopify.
custom.edit_subscriptions_frequency → janela máxima por frequência
custom.account_cancellation_reasons → motivos oferecidos ao cliente
O calendário na tela já limita as datas selecionáveis, para o cliente não descobrir o limite depois de salvar. Mas a mesma regra é conferida de novo no servidor, antes de a requisição seguir adiante. A checagem do cliente é conveniência; a do servidor é o que de fato vale. Como a regra é dinâmica, ela acaba tendo que existir nos dois lugares, lida da mesma origem.
06Reativar é conferir o catálogo de hoje
Uma assinatura cancelada há oito meses é uma foto do catálogo de oito meses atrás. O produto pode ter saído de linha, a cor pode ter sido aposentada, a fragrância pode ter sido reformulada. Reativar não é só ligar de volta, é checar se aquela foto ainda descreve alguma coisa que existe.
O retorno é parcial de propósito. O que dá para reativar, reativa; o resto volta para a tela como uma lista de itens que precisam de uma escolha nova do cliente. Reativar cinco de sete e explicar os outros dois funciona melhor do que recusar o conjunto inteiro.
07Recomprar um produto que é único
Em catálogo comum, comprar de novo é jogar uma variante no carrinho. Com produto personalizado, a variante é só a casca: o que identifica o item são as propriedades de linha, cor, base, fragrância, objetivos, nome do perfil. Recomprar, aqui, é remontar um conjunto de propriedades que ainda seja válido.
Por isso o fluxo pergunta antes de mandar qualquer coisa para o carrinho. O cliente confirma os itens, escolhe a cor de cada produto e, se alguma preferência da fórmula original saiu de linha, substitui só aquele pedaço.
A entrega no carrinho é onde a fronteira de domínio aparece de forma mais concreta. O carrinho de conversão fica na loja, não na aplicação, e um fetch entre origens é barrado. Então a recompra sai como um POST de formulário para o endpoint de carrinho da loja, com cada propriedade num campo:
<input name="items[][id]" value="4838…">
<input name="items[][quantity]" value="1">
<input name="items[][properties][__color]" value="Rosé">
<input name="items[][properties][__profile_name]" value="of Beauty">
É o caminho que o navegador permite e que a loja já entende sem nenhuma adaptação. O cliente cai no carrinho com a fórmula dele montada.
08A área de conta não cacheia nada
Hydrogen é feito para cachear bastante: página de produto, coleção, busca. A área de conta faz o contrário. Depois de mudar a data da entrega, o cliente tem que ver a data nova, não uma resposta de dez segundos atrás dizendo outra coisa.
As rotas de dados da conta respondem com no-store, a rota revalida sempre, e toda mutação bem-sucedida chama a revalidação do loader antes de a interface dizer que deu certo. O que aparece na tela vem sempre de uma leitura nova do Recharge: não usamos atualização otimista nesse fluxo.
É consequência direta da primeira decisão. Se o Recharge é a fonte de verdade, a tela não chuta o que acha que aconteceu; ela pergunta de novo.
09O roteiro, na ordem que funciona
Essa é a sequência que usamos, na ordem em que faz sentido fazer. Os três primeiros passos são chatos e não produzem nada visível, mas errar a ordem deles custa retrabalho: domínio, login e sessão dependem um do outro, e é ruim descobrir isso depois de já ter tela pronta.
No fim, o conjunto de variáveis de ambiente é um bom resumo do que precisa existir para tudo isso funcionar:
# Shopify
PUBLIC_STORE_DOMAIN
PUBLIC_CHECKOUT_DOMAIN
PUBLIC_STOREFRONT_ID
PUBLIC_STOREFRONT_API_TOKEN
PRIVATE_STOREFRONT_API_TOKEN
PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID
PUBLIC_CUSTOMER_ACCOUNT_API_URL
SHOP_ID
SESSION_SECRET assina o cookie de sessão
# Recharge
RECHARGE_URL base da API
RECHARGE_API_TOKEN admin; só no worker
# Camada de escrita
SUBSCRIPTION_SERVICE_URL endpoint que aplica o envelope
SECRET_KEY_GENERATE_JWT segredo que assina o JWT
O que levamos desse projeto
O primeiro aprendizado é sobre disciplina: escolher uma fonte de verdade e não trair ela. A vontade de guardar uma cópia local do estado das assinaturas só para a tela abrir mais rápido é a origem de uma família inteira de bug em que a interface e a cobrança discordam, e esses são os piores de depurar porque cada lado está certo dentro da própria versão dos fatos.
O segundo é que vale manter a tela falando de intenção. Quando uma ação do usuário vira várias operações no modelo, juntar tudo num envelope único tira a ordem e a atomicidade de dentro do componente, que é o último lugar onde essas duas coisas deveriam morar.
E o terceiro: reativar, recomprar, repetir, qualquer funcionalidade que traga o passado de volta é na prática uma reconciliação com o catálogo atual. Tratar isso como parte do fluxo normal, e não como caminho de erro, foi o que mais diferença fez na percepção de cuidado do produto.
Projetamos e construímos portais de assinatura em Hydrogen, Recharge e Shopify Subscriptions, com regra de negócio editável pelo time e escrita auditável.
Falar com a engenharia ↗