Class: Www::WarrantyRegistrationsController

Inherits:
BasePortalController show all
Defined in:
app/controllers/www/warranty_registrations_controller.rb

Overview

Public warranty registration (Warranty Registration v2, Phase 2). Replaces
the eleven per-product MailForm::Warranty* contact forms with an
order-aware flow:

  1. new — order lookup (order number + email/ZIP verifier), or the
    registration form when a verified order token / manual
    mode is present.
  2. lookup — runs Warranty::OrderLookup; success redirects back to
    new carrying a short-lived signed order token, so the
    order id itself never round-trips tamperable.
  3. create — Turnstile + SpamCheck gated; Warranty::PublicRegistration
    resolves the end-consumer customer, saves the warranty,
    claims uploads, and emails staff.
  4. thank_you— confirmation + (Phase 3) review prompt for matched orders.

The lookup endpoint is rate-limited in config/initializers/380_rack_attack.rb.

Constant Summary collapse

ORDER_TOKEN_PURPOSE =

Signing purpose for fully verified order tokens (email/ZIP verifier matched).

:warranty_registration
LIMITED_ORDER_TOKEN_PURPOSE =

Issued when the lookup had no matching email/ZIP verifier: the form
shows the order's products but never prefills the customer's contact
details. Two distinct signing purposes keep the "verified" bit
tamper-proof.

:warranty_registration_products_only
ORDER_TOKEN_TTL =

Lifetime of issued order tokens before they expire.

2.hours

Constants included from Controllers::MasqueradeGuarded

Controllers::MasqueradeGuarded::DEFAULT_BLOCK_MESSAGE

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

Instance Method Summary collapse

Methods inherited from BasePortalController

#current_ability, #portal_party, #set_catalog, #set_webpack

Methods included from Controllers::MasqueradeGuarded

block_while_masquerading, #masquerade_blocks?

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #skip_layout_for_turbo_frame?, #stamp_impersonation_context, #tab_frame_breakout_request?, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #registration_lead_type, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TurboSafeRedirect

#redirect_to

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Instance Method Details

#createObject

POST — persist the registration.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'app/controllers/www/warranty_registrations_controller.rb', line 87

def create
  @order = order_from_token

  # One-shot per order: a second submission (stale tab, double-click,
  # replay) bounces to the status page instead of creating duplicates.
  if @order && Warranty.where(order_id: @order.id).exists?
    redirect_to new_warranty_registration_path(order_token: params[:order_token].presence)
    return
  end

  if spam_check_failed?
    flash[:error] = 'There was an error submitting your request. Please call us at (800) 875-5285.'
    redirect_to new_warranty_registration_path
    return
  end

  submission = Warranty::RegistrationParams.new(params, order: @order)
  result = Warranty::PublicRegistration.call(
    order: @order,
    registrant: submission.registrant_attributes,
    warranties_attributes: submission.warranties_attributes,
    warranty_attributes: submission.shared_warranty_attributes,
    upload_ids: submission.upload_ids,
    # Claims are restricted to files this browser session uploaded
    # (tracked by Www::UploadsController) — a guessed id attaches nothing.
    allowed_upload_ids: Array(session[:public_upload_ids])
  )

  if result.success
    session[:warranty_registration_ids] = [result.warranty.id]
    redirect_to thank_you_warranty_registrations_path
  elsif result.already_registered
    # Concurrent double-submit caught under the order lock — same
    # treatment as the pre-check: show the coverage-status page.
    redirect_to new_warranty_registration_path(order_token: params[:order_token].presence)
  else
    # Log the rejection reason — the flash renders client-side only, and
    # "customer couldn't register" reports are undebuggable without it.
    Rails.logger.warn "[WarrantyRegistration] create rejected: #{result.error.inspect} " \
                      "(order=#{@order&.id}, manual=#{@order.nil?})"
    flash.now[:error] = result.error.presence || 'There was a problem submitting your registration. Please try again.'
    @manual = @order.nil?
    # Re-render with everything the customer typed — the views read the
    # per-row/repeater values back from @submission.
    @submission = submission
    @warranty = Warranty.new(submission.shared_warranty_attributes
                                       .except('installers', *Warranty::PublicRegistration::PRODUCT_ATTR_KEYS))
    load_product_line_groups
    render :register, status: :unprocessable_content
  end
end

#lookupObject

POST — order lookup with verifier. Never discloses whether the order
number exists without a matching verifier (see Warranty::OrderLookup).



73
74
75
76
77
78
79
80
81
82
83
84
# File 'app/controllers/www/warranty_registrations_controller.rb', line 73

def lookup
  result = Warranty::OrderLookup.call(lookup_params.to_h.symbolize_keys)

  if result.success
    purpose = result.verified ? ORDER_TOKEN_PURPOSE : LIMITED_ORDER_TOKEN_PURPOSE
    token = result.order.signed_id(purpose: purpose, expires_in: ORDER_TOKEN_TTL)
    redirect_to new_warranty_registration_path(order_token: token)
  else
    flash[:error] = result.error
    redirect_to new_warranty_registration_path(q: lookup_params[:query])
  end
end

#newObject

Step 1 (lookup form), or step 2 (registration form) when a verified
order token or explicit manual mode is present.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'app/controllers/www/warranty_registrations_controller.rb', line 37

def new
  @order = order_from_token
  # Optional query flag — `expect` would 400 when it's absent.
  @manual = params[:manual].to_b # rubocop:disable Rails/StrongParametersExpect
  @warranty = Warranty.new(product_line: params[:product_line].presence)
  # A failed lookup carries the typed order number via ?q= — keep it so
  # the manual registration stores what the customer actually has.
  @warranty.external_order_number = params[:q].presence if @manual && @order.nil?
  # An already-registered order lands on a status page with the coverage
  # summary — the registration is one-shot; changes go through support/CRM.
  @order_warranty = @order && Warranty.find_by(order_id: @order.id)
  @order_products = @order_warranty&.products&.order(:id).to_a
  load_product_line_groups
  return render :already_registered if @order_warranty

  render(@order || @manual ? :register : :new)
end

#sku_lookupObject

GET (JSON) — manual-flow product lookup: resolves a typed SKU, SKU
alias, Amazon ASIN, UPC, or EAN to the catalog item so the form can
prefill the product line and name. Catalog data is public (the
storefront lists it), so no verifier is needed; rack-attack keeps
scraping in check. product_line is null for items that aren't
registrable on their own (thermostats, accessories).



61
62
63
64
65
66
67
68
69
# File 'app/controllers/www/warranty_registrations_controller.rb', line 61

def sku_lookup
  item = find_item_by_code(params[:sku].to_s.strip)
  return render json: { found: false } unless item

  product_line = Warranty.product_line_for_item(item)
  render json: { found: true, name: item.name, sku: item.sku,
                 product_line: product_line,
                 product_line_label: product_line && Warranty::PRODUCT_LINE_LABELS[product_line] }
end

#thank_youvoid

This method returns an undefined value.

Confirmation page after registration: shows the registered warranty's coverage
(the whole order's displayable line items for matched orders) and a review prompt.
Redirects to the registration form when no completed registration is in the session.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'app/controllers/www/warranty_registrations_controller.rb', line 143

def thank_you
  @warranty = Warranty.where(id: Array(session.delete(:warranty_registration_ids))).order(:id).first
  @products = @warranty&.products&.order(:id).to_a
  if @warranty.nil?
    redirect_to new_warranty_registration_path
    return
  end

  # Matched orders show the WHOLE order's coverage — including the
  # accessories/controls that are covered automatically — not just the
  # rows that were registered.
  @order = @warranty.order
  if @order
    @order_products = @products
    @displayable_line_items = Warranty.displayable_line_items(@order)
  end

  @review_prompt_url = Warranty::ReviewPrompt.call(warranty: @warranty)
end