Class: MyCartsController
- Inherits:
-
BasePortalController
- Object
- ActionController::Base
- ApplicationController
- BasePortalController
- MyCartsController
- Includes:
- MyAccountHelper
- Defined in:
- app/controllers/my_carts_controller.rb
Overview
Controller: my carts.
Constant Summary collapse
- CHECKOUT_STARTED_CART_ID_SESSION_KEY =
Session key recording which cart already fired the checkout-started event,
sotrack_checkout_startedonly tracks once per cart. 'checkout_started_cart_id'
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
-
#add_coupon(coupon_code) ⇒ String?
protected
Attempts to apply a coupon to the cart.
-
#add_item ⇒ void
Adds a single SKU to the cart and exposes product info for the add-to-cart toast and analytics.
-
#add_multiple_items ⇒ void
Adds several items to the cart from
params[:items](sku|qtypairs) or a JSON request body, resolving SKU aliases and reporting skipped SKUs. -
#adjust_sku_quantity ⇒ void
Adjusts the quantity of a SKU in the cart (or a room within the cart), refreshing the cart in place via Turbo Stream.
-
#apply_coupon ⇒ void
Applies
params[:coupon_code]to the cart and refreshes either the checkout coupon box or the whole cart depending oncoupon_context. -
#cart_add_item(sku, qty = nil, room_configuration_id = nil) ⇒ void
protected
Adds a single SKU to the cart, recording recently added items and flashing an error when the SKU is not in the catalog.
-
#cart_conflict ⇒ Object
Display cart conflict resolution page when user logs in with items in both guest and account carts.
-
#check_address_and_locale ⇒ void
Switches the visitor's catalog when the requested locale's country differs from the address country, warning about cart items dropped by the switch.
-
#check_existing_account ⇒ void
JSON probe returning 200 when the given email already belongs to an account (used to prompt guests to sign in during checkout).
-
#check_inventory_levels ⇒ Boolean
Caps B-stock line-item quantities at available inventory, flashing a warning when any quantity was reduced.
-
#checkout ⇒ void
Entry point of the checkout flow: routes to the cart, service scheduling, or customer-info step depending on cart state and inventory levels.
-
#checkout_update ⇒ void
Saves the checkout form (with spam checking) and proceeds to the shipping step, re-rendering customer-info with errors on failure.
-
#customer_info ⇒ void
Renders the customer-info step of checkout.
-
#payment ⇒ void
Renders the payment step of checkout, rebuilding the discount and payment options from a freshly reloaded cart.
-
#remove_all_line_items ⇒ void
Empties the cart entirely.
-
#remove_coupon ⇒ void
Removes a customer-applied coupon (and its discount) from the cart.
-
#remove_from ⇒ void
Placeholder action for removing an item from a specific container.
-
#remove_item ⇒ void
Removes a line item (and its related SmartInstall service lines) from the cart, refreshing the cart in place.
-
#remove_room ⇒ void
Removes a room configuration (and its line items) from the cart.
-
#resolve_cart_conflict ⇒ Object
Handle user's choice for cart conflict resolution.
-
#retrieve ⇒ void
Cart-recovery entry point: attempts magic-link sign-in in a before action, then redirects to the cart (preserving any referral code).
-
#schedule_service ⇒ void
Renders the service-scheduling step of checkout.
-
#schedule_service_update ⇒ void
Saves the service-scheduling form and proceeds to the customer-info step, re-rendering the form with errors on failure.
-
#set_cart_qty ⇒ void
Stores the current cart id and item count in the session for the navbar cart badge, and loads material alerts.
-
#shipping ⇒ void
Renders the shipping step of checkout, building the per-delivery shipping options hash and auto-selecting economy/freight options where applicable.
-
#shipping_address ⇒ void
Renders the shipping-address selection, filtered to the store's country with the warehouse address appended.
-
#shipping_update ⇒ void
Placeholder action for shipping updates.
-
#show ⇒ void
Renders the cart page, stamping
first_cart_view_dateon first view and surfacing quote/room load errors when arriving from a quote link. -
#update ⇒ void
Updates cart quantities, shipping selections, and coupon codes from the cart form, then either refreshes the cart in place or proceeds to checkout depending on the submitted button.
-
#update_from_payment ⇒ void
Applies the shipping selection submitted from the payment step via
Shipping::UpdateShippingMethod. -
#update_shipping_address ⇒ void
Saves the shipping address chosen or entered on the shipping step.
-
#update_shipping_address_from_modal ⇒ void
Saves the shipping address submitted from the address modal.
Methods included from MyAccountHelper
#account_nav_active?, #can_list_contact_resources?, #is_current_user_or_customer_login_email?, #setup_contact, #setup_customer_account, #sorted_contact_points
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
Methods included from Controllers::SubdomainDetection
#is_crm_request?, #is_www_request?, #json_request?
Methods included from Controllers::TurboSafeRedirect
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
#add_coupon(coupon_code) ⇒ String? (protected)
Attempts to apply a coupon to the cart.
814 815 816 817 818 819 820 821 822 823 824 825 826 827 |
# File 'app/controllers/my_carts_controller.rb', line 814 protected def add_coupon(coupon_code) return nil if coupon_code.blank? res = @cart.customer_can_apply_coupon?(coupon_code) coupon = res[:coupon] msg = res[:message] return msg || "Coupon '#{coupon_code}' is invalid" unless coupon result = Coupon::ApplyCoupon.new.perform(@cart, { coupon_id: coupon.id }) return result.errors unless result.success? nil # Success - no error end |
#add_item ⇒ void
This method returns an undefined value.
Adds a single SKU to the cart and exposes product info for the add-to-cart
toast and analytics.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'app/controllers/my_carts_controller.rb', line 121 def add_item sku_array = [{ sku: params[:sku], qty: params[:quantity], room_configuration_id: params[:room_configuration_id] }] begin @recently_added = @cart.add_multiple_items(sku_array) rescue Order::ItemNotFound => e flash.now[:error] = e..presence || 'We could not find that product in the catalog.' @recently_added = [] end @added_item = @recently_added&.first # Reload line_items to get fresh count after add_multiple_items saves @cart.line_items.reload # Store product info for analytics and toast display # Use ViewProductCatalog for proper pricing/category data catalog = Catalog.locale_to_catalog vpc = ViewProductCatalog.find_by(item_sku: params[:sku], catalog_id: catalog.id) @product_info = { sku: params[:sku], name: vpc&.item_name, image: vpc&.item&.primary_image_url(size: '100x100'), category: vpc&.product_category_name, price: vpc&.effective_price&.to_f, quantity: params[:quantity] || 1 } respond_to do |format| format.turbo_stream format.json do render json: cart_array end format.html do redirect_to my_cart_path end end end |
#add_multiple_items ⇒ void
This method returns an undefined value.
Adds several items to the cart from params[:items] (sku|qty pairs) or
a JSON request body, resolving SKU aliases and reporting skipped SKUs.
Optionally applies params[:coupon_code] afterwards.
82 83 84 85 86 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 |
# File 'app/controllers/my_carts_controller.rb', line 82 def add_multiple_items sku_array = [] if params[:items].present? sku_array = Array(params[:items]).map do |item| sku, qty = item.split('|') { sku:, qty: } end elsif (payload = request.body.read).present? skus = MultiJson.load(payload, symbolize_names: true) sku_array = skus.map { |sku:, qty:| { sku:, qty: } } end resolved_items, skipped_items = resolve_skus_for_cart(sku_array) @cart.add_multiple_items(resolved_items) if resolved_items.present? if skipped_items.present? skipped_skus = skipped_items.pluck(:sku).join(', ') flash[:warning] = "We could not add the following items to your cart: #{skipped_skus}." end if params[:coupon_code] coupon_error = add_coupon(params[:coupon_code]) flash[:error] = coupon_error if coupon_error end respond_to do |format| format.json do render json: cart_array end format.html do redirect_to my_cart_path end end end |
#adjust_sku_quantity ⇒ void
This method returns an undefined value.
Adjusts the quantity of a SKU in the cart (or a room within the cart),
refreshing the cart in place via Turbo Stream.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'app/controllers/my_carts_controller.rb', line 161 def adjust_sku_quantity if params[:sku].present? sku = params[:sku] quantity = params[:quantity].to_i room_configuration_id = params[:room_configuration_id] if sku.present? && quantity.positive? && quantity < 100 # find catalog by locale if (ci = @cart.catalog.catalog_items.public_catalog_items.by_skus(sku).first) @cart.recalculate_shipping = true # doesn't hurt to set it @cart.recalculate_discounts = true # ensure tier2 and auto-apply discounts are calculated @cart.force_total_reset = true if quantity.positive? @new_line = @cart.add_line_item(catalog_item_id: ci.id, quantity:, room_configuration_id:) # , :do_not_autosave => true) logger.debug "my_carts_controller#adjust_sku_quantity: Add SKU: #{sku} #{quantity} @new_line.inspect: #{@new_line.inspect}" logger.debug "my_carts_controller#adjust_sku_quantity: Add SKU: #{sku} #{quantity} @new_line.item.inspect: #{@new_line.item.inspect}" @cart.save! else if room_configuration_id && (room = RoomConfiguration.find(room_configuration_id)) container = room container_name = 'room' else container = @cart container_name = 'cart' end li = container.line_items.find { |l| l.sku == sku } if li container.remove_line_item(li, quantity.abs) logger.debug "my_carts_controller#adjust_sku_quantity: Remove SKU: #{sku} #{quantity.abs} li.inspect: #{li.inspect}" logger.debug "my_carts_controller#adjust_sku_quantity: Remove SKU: #{sku} #{quantity.abs} li.item.inspect: #{li.item.inspect}" @cart.save else flash[:error] = "We could not find sku #{sku} in #{container_name} line items." end end else flash[:error] = "We could not find sku #{sku} in catalog." end else flash[:error] = "sku #{sku} not found in catalog or invalid quantities specified." end end respond_to do |format| # Refresh the cart in place — replace #cart-body, no navigation. See # doc/tasks/202605212322_CART_TURBO_STREAMS.md. format.turbo_stream do @cart.line_items.reload check_material_alerts render partial: 'cart_streams' end format.html do redirect_to my_cart_path end format.json do render json: cart_array end end end |
#apply_coupon ⇒ void
This method returns an undefined value.
Applies params[:coupon_code] to the cart and refreshes either the
checkout coupon box or the whole cart depending on coupon_context.
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 |
# File 'app/controllers/my_carts_controller.rb', line 762 def apply_coupon if params[:coupon_code].present? = add_coupon(params[:coupon_code]) if flash[:error] = else # A valid coupon that applied silently (e.g. capped out by an existing # discount) gave no feedback at all before — confirm it landed. flash[:info] = "Coupon #{params[:coupon_code]} applied." end end respond_to do |format| # TurboStreamFlashable appends the flash message to the stream response. format.turbo_stream do if params[:coupon_context] == 'checkout' # Checkout sidebar: refresh just the coupon-entry box (existing behavior). render turbo_stream: turbo_stream.update('cart_discounts', partial: 'coupon_entry', locals: { coupon_context: 'checkout' }) else # Cart page: refresh the whole cart so the savings panel and totals # reflect the coupon — not just the coupon-entry box. check_material_alerts render partial: 'cart_streams' end end format.html { redirect_back_or_to(my_cart_path) } end end |
#cart_add_item(sku, qty = nil, room_configuration_id = nil) ⇒ void (protected)
This method returns an undefined value.
Adds a single SKU to the cart, recording recently added items and flashing
an error when the SKU is not in the catalog.
799 800 801 802 803 804 805 806 807 808 809 |
# File 'app/controllers/my_carts_controller.rb', line 799 protected def cart_add_item(sku, qty = nil, room_configuration_id = nil) # add item return if sku.blank? @recently_added_items ||= [] begin @recently_added_items = @cart.add_multiple_items([{ sku:, qty:, room_configuration_id: }]) rescue Order::ItemNotFound flash[:error] = "We could not find sku #{sku} in catalog." end end |
#cart_conflict ⇒ Object
Display cart conflict resolution page when user logs in with items in both guest and account carts
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
# File 'app/controllers/my_carts_controller.rb', line 392 def cart_conflict @guest_cart_id = session[:cart_conflict_guest_cart_id] @return_path = params[:return_path] || session[:cart_conflict_return_path] || customer_info_my_cart_path session[:cart_conflict_return_path] = @return_path prepare_cart_conflict_state return if performed? @resolver = Cart::ConflictResolver.new(account_cart: @account_cart, guest_cart: @guest_cart) if @resolver.conflict? @conflict_summary = @resolver.conflict_summary render 'my_carts/cart_conflict' else auto_resolve_cart_conflict end end |
#check_address_and_locale ⇒ void
This method returns an undefined value.
Switches the visitor's catalog when the requested locale's country differs
from the address country, warning about cart items dropped by the switch.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'app/controllers/my_carts_controller.rb', line 224 def check_address_and_locale if params[:locale].downcase.include?(params[:country]) head :ok else logger.info "[set_locale] -> check_address_and_locale wants to assign new catalog, guest user locale: #{@context_user.locale}, requested locale #{I18n.locale}" new_catalog = params[:country] == 'ca' ? Catalog.find(2) : Catalog.find(1) goods_before_switch = cart_goods_labels # snapshot before fix_catalog drops items with no equivalent result = @context_user.change_catalog(new_catalog) || {} if result.catalog_assigned? logger.info "[set_locale] -> check_address_and_locale, assigned new catalog #{new_catalog.id}" @context_user.reload flash_dropped_cart_items(goods_before_switch, params[:country] == 'ca' ? 'Canada' : 'the United States') locale = params[:country] == 'ca' ? :'en-CA' : :'en-US' redirect_url = cms_link(URI(request.referer).path, locale, host: WEB_HOSTNAME, scheme: 'https') render json: { redirect_url: redirect_url } else logger.error "[set_locale] -> check_address_and_locale, assigned new catalog #{new_catalog.id} failed #{result..to_sentence}" render json: { error: result..to_sentence }, status: :unprocessable_content end end end |
#check_existing_account ⇒ void
This method returns an undefined value.
JSON probe returning 200 when the given email already belongs to an
account (used to prompt guests to sign in during checkout).
747 748 749 750 751 752 753 754 755 756 757 |
# File 'app/controllers/my_carts_controller.rb', line 747 def check_existing_account email = params[:email] return nil if fully_logged_in? return nil if email.blank? return unless Account.where(email:).any? respond_to do |format| format.json { head :ok } end end |
#check_inventory_levels ⇒ Boolean
Caps B-stock line-item quantities at available inventory, flashing a
warning when any quantity was reduced.
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 |
# File 'app/controllers/my_carts_controller.rb', line 723 def check_inventory_levels # Only check inventory levels of b-stock return false if @cart.line_items.bstock.blank? @items_in_cart = @cart.line_items.bstock.parents_only.non_shipping items_below_inventory = false @items_in_cart.each do |li| available_stock = li&.catalog_item&.qty_available || 0 if li.quantity <= available_stock # All good, we have inventory else ErrorReporting.error("Not enough inventory for an item in the cart. Item #{li.sku}. Qty added #{li.quantity}. Qty available #{available_stock}") # We update the cart to max out the available inventory li.update(quantity: available_stock) flash[:warning] = "Unfortunately we don't have enough stock available for the product '<i>#{li.name}</i>'. We updated the quantity in your cart to reflect the maximun stock available.".html_safe items_below_inventory = true end end items_below_inventory end |
#checkout ⇒ void
This method returns an undefined value.
Entry point of the checkout flow: routes to the cart, service scheduling,
or customer-info step depending on cart state and inventory levels.
378 379 380 381 382 383 384 385 386 387 388 389 |
# File 'app/controllers/my_carts_controller.rb', line 378 def checkout if @cart.empty? redirect_to my_cart_path elsif check_inventory_levels flash.keep(:warning) redirect_to my_cart_path elsif @cart.includes_schedulable_service? redirect_to schedule_service_my_cart_path else redirect_to customer_info_my_cart_path end end |
#checkout_update ⇒ void
This method returns an undefined value.
Saves the checkout form (with spam checking) and proceeds to the shipping
step, re-rendering customer-info with errors on failure.
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 |
# File 'app/controllers/my_carts_controller.rb', line 609 def checkout_update params[:checkout_form] ||= {} params[:checkout_form][:order_id] = @cart.id address_option = params['customer-addresses'] params[:checkout_form][:shipping_address][:recipient] = "#{params[:shipping_first_name]} #{params[:shipping_last_name]}" if params[:checkout_form][:shipping_address].present? params[:checkout_form][:billing_address][:recipient] = params[:checkout_form][:contact_name].to_s params[:checkout_form][:address_id] = address_option @checkout_form = Checkout::CheckoutForm.new(params[:checkout_form]) spam_checker = SpamCheck.new is_spam = spam_checker.process(request:, author_email: @checkout_form.email, phone_number: @checkout_form.phone) success = !is_spam && @checkout_form.save if is_spam # We don't want to say why its spam @checkout_form.errors.add(:checkout, ": An unknown error occurred, please contact us to complete your order. Reference cart number SC#{@cart.id}") @cart.quick_note spam_checker.spam_results_to_s end logger.info "Saving cart #{@cart.id}, is_spam: #{is_spam}, cart save: #{success}, errors are #{@checkout_form.errors_to_s}" initialize_address_book # Make sure all addresses are set up if success @cart = @cart.reload track_checkout_started begin logger.info 'Cart successfully saved.' if @cart.shipping_address.nil? @cart.update(shipping_address_id: @addresses.first.id) @cart.save end @cart = @cart.reload redirect_to shipping_my_cart_path rescue StandardError => e ErrorReporting.error(e) redirect_to shipping_my_cart_path end else @cancel_path = checkout_my_cart_path @new_address = true if address_option.present? && address_option == 'new' msg = "Error during checkout with cart #{@cart.id}. The form could not be saved. is_spam: #{is_spam}. #{@checkout_form.errors_to_s}. Params sent by the customer are: #{params[:checkout_form]}" ErrorReporting.warning(msg) logger.warn msg # Force HTML format - Turbo Drive handles 422 HTML responses by replacing page content render 'my_carts/new_checkout/customer_info', status: :unprocessable_content, formats: [:html] end end |
#customer_info ⇒ void
This method returns an undefined value.
Renders the customer-info step of checkout.
443 444 445 446 447 448 449 450 451 452 453 |
# File 'app/controllers/my_carts_controller.rb', line 443 def customer_info if @cart.empty? redirect_to my_cart_path else logger.info "Checkout for cart #{@cart.id}" @show_tab = params[:show_tab] @material_alerts = @cart.get_material_alerts(for_www: true, for_cart: true) setup_checkout render 'my_carts/new_checkout/customer_info' end end |
#payment ⇒ void
This method returns an undefined value.
Renders the payment step of checkout, rebuilding the discount and payment
options from a freshly reloaded cart.
575 576 577 578 579 580 581 582 583 584 585 586 |
# File 'app/controllers/my_carts_controller.rb', line 575 def payment if @cart.empty? redirect_to my_cart_path else @cart.reload.reset_discount # Temporary fix while we fix the underlying issues with the total amount not being correct @cart.reload # Reload after reset_discount to pick up DB-calculated total @hide_shipping = false @payment_options = @cart.('cart') @payment = @cart.payments.build(category: 'Credit Card') render 'my_carts/new_checkout/payment' end end |
#remove_all_line_items ⇒ void
This method returns an undefined value.
Empties the cart entirely.
295 296 297 298 299 |
# File 'app/controllers/my_carts_controller.rb', line 295 def remove_all_line_items @cart.reset_cart flash[:info] = 'Your shopping cart was emptied.' redirect_to_return_path_or_default my_cart_path end |
#remove_coupon ⇒ void
This method returns an undefined value.
Removes a customer-applied coupon (and its discount) from the cart.
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 |
# File 'app/controllers/my_carts_controller.rb', line 687 def remove_coupon coupon_code = params[:coupon_code] coupon = @cart.customer_applied_coupons.where(code: coupon_code).first discount = nil discount = @cart.discounts.where(coupon_id: coupon.id).first if coupon.present? if coupon && discount res = Coupon::DeleteDiscount.new.perform(discount) if res.success? flash[:info] = "Coupon code #{coupon_code} removed." else flash[:error] ||= '' flash[:error] += res.errors end else flash[:error] ||= '' flash[:error] += "No customer applied coupons with code #{coupon_code} found!" end respond_to do |format| format.html { redirect_back_or_to my_cart_path } end end |
#remove_from ⇒ void
This method returns an undefined value.
Placeholder action for removing an item from a specific container.
683 |
# File 'app/controllers/my_carts_controller.rb', line 683 def remove_from; end |
#remove_item ⇒ void
This method returns an undefined value.
Removes a line item (and its related SmartInstall service lines) from the
cart, refreshing the cart in place.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'app/controllers/my_carts_controller.rb', line 249 def remove_item line_item = LineItem.find_by(id: params[:line_item_id].to_i) if line_item if line_item.is_smartinstall_service? @cart.line_items.select { |li| li.id == line_item.id || li.(line_item.sku) }.each(&:destroy) else @cart.line_items.select { |li| li.id == params[:line_item_id].to_i }.each(&:destroy) end @cart.set_for_recalc begin @cart.save! rescue StandardError => e ErrorReporting.error(e) end @cart.line_items.reload else ErrorReporting.warning("Line item #{params[:line_item_id]} not found in cart #{@cart.id}") flash[:error] = 'We could not remove the requested line item from your cart.' end respond_to do |format| # In-place cart refresh — replace #cart-body (handles the empty-cart # transition when the last item is removed). format.turbo_stream do check_material_alerts render partial: 'cart_streams' end format.html { redirect_to my_cart_path } format.json { render json: cart_array } end end |
#remove_room ⇒ void
This method returns an undefined value.
Removes a room configuration (and its line items) from the cart.
282 283 284 285 286 287 288 289 290 291 |
# File 'app/controllers/my_carts_controller.rb', line 282 def remove_room @room = RoomConfiguration.find(params[:room_id]) res_hash = @cart.remove_room_configuration(@room) if res_hash[:status] flash[:info] = res_hash[:message] else flash[:error] = res_hash[:message] end redirect_to_return_path_or_default my_cart_path end |
#resolve_cart_conflict ⇒ Object
Handle user's choice for cart conflict resolution
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'app/controllers/my_carts_controller.rb', line 411 def resolve_cart_conflict strategy = params[:strategy]&.to_sym @guest_cart_id = session[:cart_conflict_guest_cart_id] @return_path = session[:cart_conflict_return_path] || customer_info_my_cart_path unless @guest_cart_id && strategy flash[:error] = 'Unable to resolve cart conflict. Please try again.' redirect_to my_cart_path return end @guest_cart = Order.find_by(id: @guest_cart_id) @account_cart = @cart resolver = Cart::ConflictResolver.new(account_cart: @account_cart, guest_cart: @guest_cart) result = resolver.resolve(strategy:) if result[:success] clear_cart_conflict_session flash[:success] = (result[:strategy]) redirect_to @return_path else # Preserve session[:cart_conflict_guest_cart_id] / :cart_conflict_return_path # so the customer lands back on the merge UI and can retry a different # strategy instead of losing their guest cart silently. flash[:error] = 'Unable to resolve cart conflict. Please try again.' redirect_to cart_conflict_my_cart_path end end |
#retrieve ⇒ void
This method returns an undefined value.
Cart-recovery entry point: attempts magic-link sign-in in a before action,
then redirects to the cart (preserving any referral code).
39 40 41 42 |
# File 'app/controllers/my_carts_controller.rb', line 39 def retrieve # redirect after trying to authenticate token in before action redirect_to my_cart_path(referral_code: params[:referral_code]) end |
#schedule_service ⇒ void
This method returns an undefined value.
Renders the service-scheduling step of checkout.
457 458 459 460 461 462 463 464 |
# File 'app/controllers/my_carts_controller.rb', line 457 def schedule_service if @cart.empty? redirect_to my_cart_path else @service_form = Checkout::ServiceForm.new_from_cart(@cart) render 'my_carts/new_checkout/schedule_service' end end |
#schedule_service_update ⇒ void
This method returns an undefined value.
Saves the service-scheduling form and proceeds to the customer-info step,
re-rendering the form with errors on failure.
591 592 593 594 595 596 597 598 599 600 601 602 603 604 |
# File 'app/controllers/my_carts_controller.rb', line 591 def schedule_service_update params[:service_form][:order_id] = @cart.id @service_form = Checkout::ServiceForm.new(params[:service_form]) success = @service_form.save if success redirect_to customer_info_my_cart_path else @cancel_path = schedule_service_my_cart_path ErrorReporting.error("Error while scheduling service with cart #{@cart.id}. The form could not be saved. #{@service_form.errors_to_s}. Params sent by the customer are: #{params[:service_form]}") logger.info "Error while scheduling service with cart #{@cart.id}. The form could not be saved. #{@service_form.errors_to_s}." # Force HTML format - Turbo Drive handles 422 HTML responses by replacing page content render 'my_carts/new_checkout/schedule_service', status: :unprocessable_content, formats: [:html] end end |
#set_cart_qty ⇒ void
This method returns an undefined value.
Stores the current cart id and item count in the session for the navbar
cart badge, and loads material alerts.
712 713 714 715 716 717 718 |
# File 'app/controllers/my_carts_controller.rb', line 712 def set_cart_qty @material_alerts = @cart.get_material_alerts(for_www: true, for_cart: true) @items_in_cart = @cart.line_items.parents_only.non_shipping.sum(:quantity) @items_in_cart ||= 0 session[:cart_id_number] = @cart&.id session[:cart_qty] = @items_in_cart end |
#shipping ⇒ void
This method returns an undefined value.
Renders the shipping step of checkout, building the per-delivery shipping
options hash and auto-selecting economy/freight options where applicable.
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 |
# File 'app/controllers/my_carts_controller.rb', line 469 def shipping if @cart.empty? redirect_to my_cart_path else unless @cart.is_warehouse_pickup? @cart.retrieve_shipping_costs if @cart.deliveries.empty? || @cart.deliveries.nil? @deliveries = {} @hide_shipping = false @cart.need_to_recalculate_shipping @cart.reload.deliveries.each_with_index do |delivery, index| shipping_costs_hash = delivery&.sorted_shipping_costs_www_hash economy_shipping_costs = shipping_costs_hash.fetch(:economy, []) ground_shipping_costs = shipping_costs_hash.fetch(:ground, []) faster_shipping_costs = shipping_costs_hash.fetch(:faster, []) freight_shipping_costs = shipping_costs_hash.fetch(:freight, []) @deliveries[:"delivery_#{index}"] = { ships_from: delivery.ships_from_text, delivery_id: delivery.id, economy_shipping_costs:, ground_shipping_costs:, faster_shipping_costs:, freight_shipping_costs:, selected_shipping_cost_id: delivery.selected_shipping_cost_id } end @has_ground_shipping_options = begin @deliveries.map { |a| a[1][:ground_shipping_costs].present? }.uniq.include?(true) rescue StandardError false end @found_check = false # Ensure ships_economy flag reflects the current selection when a selection already exists # This guarantees the FREE_ECONOMY_ONLINE coupon logic runs without requiring a new click @cart.deliveries.each do |dq| next if dq.selected_shipping_cost_id.blank? # CRITICAL: Ensure shipping line item exists BEFORE updating economy status # The FREE_ECONOMY_ONLINE coupon needs a shipping line to discount. # Without this, reset_discount calculates $0 discount (no shipping line) and deletes it. if dq.selected_shipping_cost && dq.line_items.none?(&:is_shipping?) dq.apply_selected_shipping_cost!(dq.selected_shipping_cost, persist: true) dq.reload end should_be_economy = dq.shipping_option&.is_override? && !dq.ships_ltl_freight? next unless @cart.respond_to?(:ships_economy) && (@cart.state == 'cart') if should_be_economy && !@cart.ships_economy Shipping::UpdateShippingMethod.new.update_economy_status(@cart, true) elsif !should_be_economy && @cart.ships_economy Shipping::UpdateShippingMethod.new.update_economy_status(@cart, false) end # Auto-select shipping options only if none are selected yet next if dq.selected_shipping_cost_id.present? shipping_costs_hash = dq.sorted_shipping_costs_www_hash if !dq.shipping_option&.is_override? || dq.ships_ltl_freight? # Freight orders: disable economy and select freight option Shipping::UpdateShippingMethod.new.update_economy_status(@cart, false) freight_shipping_costs = shipping_costs_hash.fetch(:freight, []) dq.selected_shipping_cost_id = freight_shipping_costs&.first&.id dq.save elsif !@cart.should_ship_freight? # Non-freight orders: customer qualifies for economy shipping # Auto-select first economy option and apply free shipping discount Shipping::UpdateShippingMethod.new.update_economy_status(@cart, true) economy_shipping_costs = shipping_costs_hash.fetch(:economy, []) if economy_shipping_costs.any? first_economy = economy_shipping_costs.first result = Shipping::UpdateShippingMethod.new.update_from_shipping_cost(dq, first_economy, @cart) Rails.logger.info "Auto-selected economy shipping: #{result.success? ? 'success' : result.errors}" end end end # Reload cart and deliveries data structure after auto-selection so sidebar totals reflect discounts @cart.reload @deliveries = {} @cart.deliveries.each_with_index do |delivery, index| shipping_costs_hash = delivery&.sorted_shipping_costs_www_hash economy_shipping_costs = shipping_costs_hash.fetch(:economy, []) ground_shipping_costs = shipping_costs_hash.fetch(:ground, []) faster_shipping_costs = shipping_costs_hash.fetch(:faster, []) freight_shipping_costs = shipping_costs_hash.fetch(:freight, []) @deliveries[:"delivery_#{index}"] = { ships_from: delivery.ships_from_text, delivery_id: delivery.id, economy_shipping_costs:, ground_shipping_costs:, faster_shipping_costs:, freight_shipping_costs:, selected_shipping_cost_id: delivery.selected_shipping_cost_id } end end render 'my_carts/new_checkout/shipping' end end |
#shipping_address ⇒ void
This method returns an undefined value.
Renders the shipping-address selection, filtered to the store's country
with the warehouse address appended.
659 660 661 662 663 |
# File 'app/controllers/my_carts_controller.rb', line 659 def shipping_address @addresses = @addresses.select { |a| a.country_iso3 == @cart.store.country_iso3 }.sort_by { |a| a.default_shipping ? -10 : 0 } @addresses << @cart.store.warehouse_address @hide_new_address_form = true end |
#shipping_update ⇒ void
This method returns an undefined value.
Placeholder action for shipping updates.
667 |
# File 'app/controllers/my_carts_controller.rb', line 667 def shipping_update; end |
#show ⇒ void
This method returns an undefined value.
Renders the cart page, stamping first_cart_view_date on first view and
surfacing quote/room load errors when arriving from a quote link.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'app/controllers/my_carts_controller.rb', line 47 def show @cart.first_cart_view_date ||= Time.current @cart.save flash.keep(:warning) if params[:quote_id].present? msg = nil @quote = (begin @context_user.customer.quotes.find(params[:quote_id]) rescue StandardError nil end) msg = 'Could not find quote. Your session may have expired.' if @quote.nil? if @quote.present? && params[:room_id].present? @room = (begin @quote.room_configurations.find(params[:room_id]) rescue StandardError nil end) msg = 'Could not find room. Your session may have expired.' if @room.nil? end flash[:info] = msg if msg.present? end check_material_alerts respond_to do |format| format.html format.json do render json: cart_array end end end |
#update ⇒ void
This method returns an undefined value.
Updates cart quantities, shipping selections, and coupon codes from the
cart form, then either refreshes the cart in place or proceeds to
checkout depending on the submitted button.
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'app/controllers/my_carts_controller.rb', line 348 def update update_cart @cart.save @cart.prune_cart_rooms set_cart_qty @redirect_to_checkout = /CheckOut|Coupon/i.match?(params['commit']) # When not navigating on to checkout, update.turbo_stream.erb refreshes # #cart-body in place — needs fresh line items + recommendations. unless @redirect_to_checkout @cart.line_items.reload check_material_alerts end respond_to do |format| format.turbo_stream format.html do if @redirect_to_checkout redirect_to checkout_my_cart_path else redirect_to my_cart_path end end format.json do render json: cart_array end end end |
#update_from_payment ⇒ void
This method returns an undefined value.
Applies the shipping selection submitted from the payment step via
Shipping::UpdateShippingMethod.
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'app/controllers/my_carts_controller.rb', line 304 def update_from_payment if params.dig(:cart, :shipping_cost_id).blank? respond_to do |format| format.turbo_stream { head :unprocessable_content } format.html { redirect_to my_cart_path } end return end # Use service to handle shipping selection # Pass params directly - the service will extract what it needs result = Shipping::UpdateShippingMethod.new.update_from_params(@cart, params) unless result.success? Rails.logger.error "Failed to update shipping: #{result.errors}" respond_to do |format| format.turbo_stream { head :unprocessable_content } format.html { redirect_to my_cart_path, alert: result.errors } end return end result..each { |msg| Rails.logger.info msg } @cart = @cart.reload if @cart.deliveries.empty? Rails.logger.error "update_from_payment: cart #{@cart.id} has no deliveries after shipping update" respond_to do |format| format.turbo_stream { head :unprocessable_content } format.html { redirect_to shipping_my_cart_path } end return end respond_to do |format| format.turbo_stream format.html { redirect_to my_cart_path } end end |
#update_shipping_address ⇒ void
This method returns an undefined value.
Saves the shipping address chosen or entered on the shipping step.
671 672 673 |
# File 'app/controllers/my_carts_controller.rb', line 671 def update_shipping_address update_method_for_cart end |
#update_shipping_address_from_modal ⇒ void
This method returns an undefined value.
Saves the shipping address submitted from the address modal.
677 678 679 |
# File 'app/controllers/my_carts_controller.rb', line 677 def update_shipping_address_from_modal update_method_for_cart end |