Como lançamos funcionalidades que mexem em dinheiro sem mudar o preço de ninguém por engano
Um erro de preços é daqueles que não se podem retirar depois de um cartão ser cobrado. Eis a disciplina que usamos para acrescentar descontos, preços por profissional e promoções ao fluxo de reservas sem que um único preço se mexa antes de nós querermos.
A maioria dos erros custa-lhe um pedido de desculpa. Um erro de preços custa-lhe um reembolso, uma devolução no cartão e um cliente que deixou de confiar no número que aparece no ecrã. O risco é desequilibrado, por isso o processo à volta de tudo o que toca em dinheiro é deliberadamente mais lento do que o resto do nosso trabalho. É esta a forma que ele tem, usando uma funcionalidade recente como exemplo: uma promoção por categoria, em que um salão pode pôr todos os tratamentos de rosto em promoção de uma só vez.
Desligado por omissão, em produção, sem fazer nada
A primeira linha da funcionalidade é um interruptor. Lê uma variável de ambiente e fica desligado por omissão. Isso significa que podemos integrar e publicar o código muito antes de ele fazer o que quer que seja, e todos os preços ficam exatamente iguais ao dia anterior. Não há um lançamento em grande com que ficar nervoso, porque o lançamento é um passo separado e aborrecido que damos quando já estamos confiantes.
export const FEATURE_LIVE = process.env.FEATURE_LIVE === "true"; // Read at the one place it matters. When the flag is off, the branch// is skipped entirely: no extra query, no price change, nothing.if (FEATURE_LIVE) { const pct = await activeCategorySalePercent(orgId, "service", service.category); if (pct > 0) fullPriceCents = applyCategorySaleCents(fullPriceCents, pct);}Uma função, um trabalho
A aritmética do desconto vive numa única função pura. Recebe cêntimos e uma percentagem e devolve cêntimos. Não tem base de dados, não tem pedido e não tem relógio, o que significa que é trivial testar todos os extremos isoladamente: uma promoção a zero, uma promoção normal, um limite no topo, um valor negativo que nunca deveria acontecer. Quando as contas cabem numa pequena função que se consegue ter toda na cabeça, deixa-se de andar a adivinhar.
export function applyCategorySaleCents( baseCents: number, salePercent: number | null | undefined,): number { const base = cents(baseCents); const pct = Math.max(0, Math.min(100, Math.round(salePercent ?? 0))); if (pct === 0) return base; return cents((base * (100 - pct)) / 100);}Recalcular no servidor, sempre
O navegador mostra um preço para o cliente saber quanto vai pagar, mas esse número é apenas visual. Quando chega uma reserva, o servidor carrega o serviço verdadeiro, resolve o preço do profissional, aplica qualquer promoção de categoria ativa e só depois calcula o desconto, tudo a partir de dados que ele próprio foi buscar. Se alguém tiver editado o pedido pelo caminho, não muda nada, porque o preço cobrado ao cartão nunca foi o preço que o navegador enviou. Este é o hábito mais importante em código que lida com dinheiro: a fronteira de confiança é o servidor, e é o servidor que faz a conta.
Tente parti-lo antes que um cliente o faça
Testes que passam dizem-lhe que os casos em que pensou funcionam. Não lhe dizem nada sobre o caso que lhe escapou. Por isso, antes de ligarmos um interruptor de preços, fazemos uma passagem adversarial cuja única função é encontrar forma de deixar o dinheiro errado. Verificações separadas atacam cada uma por um ângulo diferente: uma procura qualquer caminho que cobre a mais ao cliente, outra procura um caminho que faça o negócio perder dinheiro, outra confirma que a funcionalidade está mesmo inerte com o interruptor desligado e outra vai atrás dos arredondamentos e dos desvios nos limites. Uma promoção sobreposta a um cupão sobre um sinal é exatamente onde se esconde um cêntimo fracionado, por isso é aí que olhamos com mais atenção.
Nada disto é inteligente. É um interruptor, uma função pequena, um servidor que faz as suas próprias contas e o hábito de tentar partir o nosso próprio trabalho antes de o lançar. Tudo junto, é a diferença entre acrescentar uma funcionalidade que mexe em dinheiro e perder o sono por causa dela.