Order bump e cross-sell no checkout
TLDR: conectar checkouts existentes entre si por uma tabela de relacionamento, permitindo oferecer produtos complementares (order bump), upgrades (upsell) ou alternativas mais baratas (downsell) durante o checkout.
Contexto
O order bump é uma funcionalidade de cross-selling que oferece produtos adicionais relacionados ao produto principal durante o checkout, com o objetivo de aumentar o ticket médio.
Objetivos
- Um checkout principal pode ter múltiplos checkouts relacionados como cross-sell.
- Cada relacionamento tem configuração própria de promoção, valor de referência, descrição personalizada e ordem de exibição.
- Seguir o padrão MVC da aplicação Rails e os padrões já estabelecidos na codebase para relacionamentos e serialização.
Fora de escopo
— (não registrado na spec original)
Mudanças
Tabela checkout_cross_sells
```ruby class CreateCheckoutCrossSells < ActiveRecord::Migration[8.0] def change create_table :checkout_cross_sells do |t| t.references :primary_checkout, null: false, foreign_key: { to_table: :checkouts } t.references :cross_sell_checkout, null: false, foreign_key: { to_table: :checkouts } t.text :description t.decimal :reference_value, precision: 10, scale: 2 t.decimal :promotion_value, precision: 10, scale: 2 t.integer :kind, null: false t.integer :order, null: false, default: 0 t.string :pid, null: false
t.timestamps
t.index [:primary_checkout_id, :cross_sell_checkout_id],
name: "index_checkout_cross_sells_unique", unique: true
t.index :pid, unique: true
t.index [:primary_checkout_id, :order]
end end end ```
Modelo CheckoutCrossSell
Em app/models/checkout_cross_sell.rb, com include Pidable:
- Enum
kind:order_bump: 0,upsell: 1,downsell: 2 - Relações:
belongs_to :primary_checkoutebelongs_to :cross_sell_checkout, ambosclass_name: 'Checkout' - Validações: presença de
description,reference_value,promotion_value,kindeorder;reference_valueepromotion_valuemaiores que 0;ordermaior ou igual a 0 - Validações customizadas:
different_checkouts(o cross-sell não pode ser o próprio checkout) esame_organization(ambos precisam pertencer à mesma organização) - Scopes:
ordered(pororder) eby_kind to_payload: devolvepid,kind,order,description,promotion_valueereference_value
Modelo Checkout
```ruby has_many :checkout_cross_sells, foreign_key: :primary_checkout_id, dependent: :destroy has_many :cross_sell_checkouts, through: :checkout_cross_sells, source: :cross_sell_checkout has_many :parent_cross_sells, class_name: ‘CheckoutCrossSell’, foreign_key: :cross_sell_checkout_id, dependent: :destroy
accepts_nested_attributes_for :checkout_cross_sells, reject_if: :all_blank, allow_destroy: true
def cross_sell_items checkout_cross_sells.ordered.map(&:to_payload) end ```
API V1
Em app/controllers/api/v1/checkout_controller.rb#show, adicionar cross_sell_items à lista de methods do as_json do checkout.
ActiveAdmin
Nova aba “Cross Sell / Order Bump” em app/admin/checkouts.rb, exibida apenas quando o registro já existe, com f.has_many :checkout_cross_sells permitindo destruição. Campos do formulário: checkout de cross-sell (select limitado aos checkouts ativos da mesma organização, exceto o próprio), tipo, descrição, valor de referência, valor promocional e ordem de exibição.
Fluxos
Criação — o usuário seleciona o checkout principal no admin, adiciona checkouts relacionados com suas configurações, o sistema valida as regras de negócio e persiste em checkout_cross_sells.
Exibição — o cliente pede GET /api/v1/checkout/:id, o sistema busca o checkout e seus cross-sells, serializa e devolve o array populado.
Logging
- Audit trail existente, rastreando
CREATE,UPDATEeDELETEdecheckout_cross_sells, com todos os campos auditados. - Log
INFOna criação e edição;WARNem tentativas de violação de regra de negócio;ERRORem falhas de validação ou processamento.
Performance
- Índices criados para as consultas frequentes, incluindo
(primary_checkout_id, order)para as consultas ordenadas. - Prevenir N+1 com
includes(:checkout_cross_sells)quando necessário. - Cache de fragmento do JSON de cross-sells por checkout, invalidado quando os cross-sells mudam.
Rollback
- Implementar o método
downna migration e fazer backup do banco antes de rodá-la em produção. - Feature toggle por variável de ambiente
ENABLE_CROSS_SELL_FEATURE, com rollout gradual por organização.
Como verificar
Testes de modelo — validações de presença e numericalidade, validações customizadas (different_checkouts, same_organization), comportamento do enum, to_payload e as relações belongs_to. No Checkout: as relações has_many, o método cross_sell_items e a aceitação de nested attributes.
Testes de controller — a resposta da API V1 inclui o campo de cross-sell, com estrutura correta e ordenação por order. No admin: formulário com nested attributes e interface de criação, edição e exclusão.
Testes de integração — criação de checkout com cross-sells pelo admin, visualização pela API V1, edição pelo admin e validação das regras de negócio.
Factory
ruby
FactoryBot.define do
factory :checkout_cross_sell do
association :primary_checkout, factory: :checkout
association :cross_sell_checkout, factory: :checkout
description { "Oferta especial!" }
reference_value { 100.00 }
promotion_value { 80.00 }
kind { :order_bump }
order { 1 }
end
end
Checklist de deploy — antes: rodar todos os testes, verificar conformidade com o RuboCop, review completo e backup do banco. Durante: migration em staging, testes de API em staging, deploy em produção, migration em produção e verificação dos logs. Depois: smoke tests das APIs, monitoramento dos logs de erro e validação com dados reais.
Documentação
Implementação: app/models/checkout_cross_sell.rb, app/admin/checkouts.rb, app/controllers/api/v1/checkout_controller.rb.
Cross-sell não recebe desconto de membro — ver as limitações em ../reference/checkout/member_discount.md.