Skip to content

fix(discounts): Stop reporting invalid coupons as applied while a campaign is active - #845

Open
vitorrgg wants to merge 1 commit into
mainfrom
fix/discounts-invalid-coupon-message
Open

vitorrgg wants to merge 1 commit into
mainfrom
fix/discounts-invalid-coupon-message

Conversation

@vitorrgg

Copy link
Copy Markdown
Member

Problema

Com uma campanha de kit ativa (leve 4 pague 3), o apply_discount sempre devolve discount_rule. Quando o cliente digita um cupom que não bate com nenhuma regra, o app não sinaliza nada, e o DiscountApplier do checkout trata "desconto + cupom digitado + sem invalid_coupon_message" como cupom aceito: mostra "Cupom de desconto aplicado com sucesso.", exibe o check verde e grava o cupom no carrinho, que segue no pedido. Relato da Tia Sônia: cliente finalizou a compra achando que o cupom de aniversário tinha sido aplicado.

Acontece com cupom inexistente, vencido ou abaixo do valor mínimo. Sem campanha ativa o checkout já mostrava "O cupom de desconto inserido é inválido.", porque aí não vem discount_rule.

Mudança

Em packages/apps/discounts/lib-mjs/apply-discount.mjs:

  • Cupom que não bate com nenhuma regra válida (inexistente, vencido, de outro cliente) e não é freebie_coupon de uma regra de brinde válida → invalid_coupon_message "O cupom de desconto inserido é inválido." (mesmo texto do i18n do checkout).
  • Cupom encontrado mas abaixo do min_amount → "Adicione mais R$ X ao carrinho para usar este cupom." (formatMoney com currency_id e lang dos params).
  • O desconto da campanha continua na resposta. O checkout mostra o aviso, não guarda o cupom, e o pedido sai só com o desconto da campanha. O functions-checkout ignora invalid_coupon_message, então o cálculo do pedido não muda.
  • Cupom encontrado que não gera desconto por outro motivo (ex.: cupom de frete antes de calcular o frete) continua como antes, sem mensagem.

O ecomplus/discounts-v2 (lojas v2) tem o mesmo comportamento e não foi alterado aqui.

Teste

  • packages/apps/discounts/tests/invalid-coupon.test.mjs (node:test, 6 casos): cupom inexistente, vencido e abaixo do mínimo falham sem a correção. Cupom válido, cupom de brinde e sem cupom continuam iguais. Roda com node --test tests/ na pasta do pacote; ainda fora do test:apps porque o pacote não tem script test.
  • Respostas passadas pelo ajv de request/response do @cloudcommerce/modules: currency_id recebe o default BRL e a resposta fica validated: true.
  • Handler real + cópia do parseDiscountOptions do DiscountApplier em 12 cenários: os 3 casos acima mostravam "Cupom de desconto aplicado com sucesso." e agora mostram o aviso; os demais não mudam.
  • Não testado em navegador nem em loja.

🤖 Generated with Claude Code

…paign is active

With a kit campaign applied (e.g. buy 4 pay 3) the response always carries a `discount_rule`,
and the storefront DiscountApplier handles any discount plus a typed coupon as an applied
coupon unless `invalid_coupon_message` is set. Unknown, expired and below-minimum coupons
showed "Cupom de desconto aplicado com sucesso." and were kept in the cart and the order.

Now `invalid_coupon_message` is returned when the typed coupon matches no valid discount rule
nor freebie coupon, and tells the missing amount when the coupon minimum is not reached.
Campaign discounts are still applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@vitorrgg vitorrgg left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisão adversarial

Veredito: aprovar com ressalvas. A correção principal funciona e não muda o cálculo do pedido (functions-checkout só lê discount_rule), mas deixa aberta uma variante do mesmo bug e o teste novo não roda no CI.

Validado: os 6 testes da PR passam. Rodei mais 15 cenários (domínio diferente, outro cliente, case mismatch, UTM + cupom errado, promoção aberta, sem lang/currency_id, amount ausente, cupom via chave direta do hidden_data, amount_field: freight) e todos se comportam como descrito, exceto os abaixo.

Required

  1. Cupom de brinde existente mas não aplicável ainda passa como "aplicado" (comentário inline em apply-discount.mjs). A checagem isFreebieCoupon ignora domain, check_product_ids e min_subtotal. Kit ativo + regra freebie_coupon: BRINDE com min_subtotal: 1000 + carrinho de R$ 280 → resposta com discount_rule do kit, sem freebie_product_ids e sem mensagem → o DiscountApplier mostra "Cupom aplicado com sucesso" e grava o cupom. É o mesmo relato da Tia Sônia. Reproduzi também com domain divergente e check_product_ids fora do carrinho.
  2. Teste não roda no CI. O pacote não tem script test, então pnpm test:apps e o test-apps.yml pulam invalid-coupon.test.mjs. Falta "test": "node --test tests/" no package.json, como em custom-shipping e loyalty-points.

Optional

  • Mudança visível fora do caso "campanha ativa" (inline). Sem kit, o checkout já mostrava "Adicione mais R$ X ao carrinho para ganhar o desconto." em variante info a partir de available_extra_discount.min_amount. Agora a mensagem do app sobrescreve essa, com texto diferente e variante warning. Vale alinhar o texto com o i18n existente.
  • amount_field: 'freight': regra com min_amount: 50 e frete R$ 20 devolve "Adicione mais R$ 30,00 ao carrinho", mas o que falta é frete. Caso raro; omitir a mensagem nesse campo ou cobrir com teste.
  • Fora deste repo: em storefront-components/DiscountApplier.js (linhas ~196-204), kit ativo + regra de brinde aberta + cupom inválido ainda emite update:coupon-code porque veio freebie_product_ids. O aviso aparece, mas o cupom vai pro carrinho e sai no pedido em extra_discount.discount_coupon. Follow-up.

Nit

  • discounts-v2 continua com o bug nas lojas v2, como a descrição admite.

Sem achado

Regressão dos caminhos antigos (cupom válido, sem cupom, kit puro, brinde válido, mensagens de não cumulativo / limite de uso / produto excluído), auth, concorrência e integrações externas: função pura, nenhuma chamada nova à API.

Não verificado

Navegador e checkout real com discount_rule + invalid_coupon_message na mesma resposta (li o DiscountApplier, não executei); combinação com outro app de desconto instalado; Node 20 (rodei com 18).

return rule.freebie_coupon?.toUpperCase() === couponCode
&& validateDateRange(rule)
&& validateCustomerId(rule, params);
});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required. Essa checagem só cobre data e cliente. Uma regra de brinde com freebie_coupon igual ao digitado mas com domain diferente, check_product_ids fora do carrinho ou min_subtotal acima do carrinho não gera freebie_product_ids e também não gera mensagem. Com kit ativo o checkout mostra "Cupom aplicado com sucesso" e grava o cupom, o mesmo sintoma que a PR corrige.

Sugestão: só considerar o cupom encontrado se a regra passou pelo mesmo filtro de getFreebiesPreview e de fato entrou em response.freebie_product_ids. Testei esta variante e ela fecha os três casos mantendo os 6 testes da PR verdes:

return rule.freebie_coupon?.toUpperCase() === couponCode
  && validateDateRange(rule)
  && validateCustomerId(rule, params)
  && mapCampaignProducts({ product_ids: rule.check_product_ids }, params).valid
  && Array.isArray(rule.product_ids) && rule.product_ids.length
  && matchFreebieRule(rule, params)
  && Array.isArray(response.freebie_product_ids)
  && rule.product_ids.every((id) => response.freebie_product_ids.includes(id));

Para min_subtotal vale devolver "Adicione mais R$ X" como no caso de min_amount.

Testes de regressão: os três cenários acima esperando invalid_coupon_message definido.

);
response.invalid_coupon_message = params.lang === 'pt_br'
? `Adicione mais ${missingAmount} ao carrinho para usar este cupom.`
: `Add ${missingAmount} more to the cart to use this coupon.`;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional. Dois pontos aqui:

  1. Isso muda comportamento também sem campanha ativa. O DiscountApplier já tratava available_extra_discount.min_amount sem discount_rule com "Adicione mais $1 ao carrinho para ganhar o desconto." em variante info; invalid_coupon_message sobrescreve isso e vira warning com outro texto. O valor daqui é até mais correto (desconta brindes), mas vale usar o mesmo texto do i18n.
  2. Com amount_field: 'freight' a mensagem diz "ao carrinho" quando o que falta é frete (regra min_amount: 50, frete R$ 20 → "Adicione mais R$ 30,00 ao carrinho"). Sugiro pular a mensagem nesse campo, com teste cobrindo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant