diff --git a/Gemfile.lock b/Gemfile.lock index 805c8563f0..c97ec7c0a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,8 +106,8 @@ GEM activerecord (6.1.4.4) activemodel (= 6.1.4.4) activesupport (= 6.1.4.4) - activerecord-import (1.2.0) - activerecord (>= 3.2) + activerecord-import (1.3.0) + activerecord (>= 4.2) activerecord-postgresql-adapter (0.0.1) pg activerecord-session_store (2.0.0) @@ -220,9 +220,10 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - ddtrace (0.53.0) - ffi (~> 1.0) + ddtrace (0.54.1) + debase-ruby_core_source (= 0.10.12) msgpack + debase-ruby_core_source (0.10.12) debugger-linecache (1.2.0) devise (4.8.0) bcrypt (~> 3.0) @@ -269,7 +270,7 @@ GEM concurrent-ruby (~> 1.1) websocket-driver (>= 0.6, < 0.8) ffaker (2.20.0) - ffi (1.15.4) + ffi (1.15.5) flipper (0.20.4) flipper-active_record (0.20.4) activerecord (>= 5.0, < 7) @@ -344,7 +345,7 @@ GEM letter_opener (1.7.0) launchy (~> 2.2) libv8-node (15.14.0.1) - listen (3.7.0) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) loofah (2.13.0) @@ -398,7 +399,7 @@ GEM parallel (1.21.0) paranoia (2.4.3) activerecord (>= 4.0, < 6.2) - parser (3.0.2.0) + parser (3.1.0.0) ast (~> 2.4.1) paypal-sdk-core (0.3.4) multi_json (~> 1.0) @@ -461,9 +462,9 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.4.2) loofah (~> 2.3) - rails-i18n (6.0.0) + rails-i18n (7.0.1) i18n (>= 0.7, < 2) - railties (>= 6.0.0, < 7) + railties (>= 6.0.0, < 8) rails_safe_tasks (1.0.0) railties (6.1.4.4) actionpack (= 6.1.4.4) @@ -471,7 +472,7 @@ GEM method_source rake (>= 0.13) thor (~> 1.0) - rainbow (3.0.0) + rainbow (3.1.1) rake (13.0.6) ransack (2.4.2) activerecord (>= 5.2.4) @@ -482,7 +483,7 @@ GEM ffi (~> 1.0) redcarpet (3.5.1) redis (4.5.1) - regexp_parser (2.1.1) + regexp_parser (2.2.0) request_store (1.5.0) rack (>= 1.4) responders (3.0.1) @@ -547,9 +548,9 @@ GEM rubocop-ast (>= 1.12.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.12.0) + rubocop-ast (1.15.1) parser (>= 3.0.1.1) - rubocop-rails (2.12.4) + rubocop-rails (2.13.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) @@ -613,7 +614,7 @@ GEM activerecord (>= 5.1) state_machines-activemodel (>= 0.8.0) stringex (2.8.5) - stripe (5.39.0) + stripe (5.42.0) temple (0.8.2) test-prof (1.0.7) test-unit (3.5.0) diff --git a/app/assets/javascripts/darkswarm/directives/auth.js.coffee b/app/assets/javascripts/darkswarm/directives/auth.js.coffee index f554e9de20..aaa8be0def 100644 --- a/app/assets/javascripts/darkswarm/directives/auth.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/auth.js.coffee @@ -3,3 +3,9 @@ angular.module('Darkswarm').directive 'auth', (AuthenticationService) -> link: (scope, elem, attrs) -> elem.bind "click", -> AuthenticationService.open '/' + attrs.auth + + window.addEventListener "login:modal:open", -> + AuthenticationService.open '/login' + + scope.$on "$destroy", -> + window.removeEventListener "login:modal:open" diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 344ca96a36..941bcf7b10 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -16,16 +16,10 @@ class SplitCheckoutController < ::BaseController helper OrderHelper def edit - return redirect_to_step unless params[:step] - - return redirect_to_guest if !spree_current_user && - !@order.distributor.allow_guest_orders? && - params[:step] != "guest" + redirect_to_step unless params[:step] end def update - return redirect_to_guest if !spree_current_user && !@order.distributor.allow_guest_orders? - if confirm_order || update_order clear_invalid_payments advance_order_state @@ -33,10 +27,9 @@ class SplitCheckoutController < ::BaseController else flash.now[:error] = I18n.t('split_checkout.errors.global') - render operations: cable_car. + render status: :unprocessable_entity, operations: cable_car. replace("#checkout", partial("split_checkout/checkout")). - replace("#flashes", partial("shared/flashes", locals: { flashes: flash })), - status: :unprocessable_entity + replace("#flashes", partial("shared/flashes", locals: { flashes: flash })) end end @@ -47,9 +40,10 @@ class SplitCheckoutController < ::BaseController end def confirm_order - return unless @order.confirmation? && params[:confirm_order] + return unless summary_step? && @order.confirmation? return unless validate_summary! && @order.errors.empty? + @order.customer.touch :terms_and_conditions_accepted_at @order.confirm! end @@ -58,17 +52,27 @@ class SplitCheckoutController < ::BaseController @order.select_shipping_method(params[:shipping_method_id]) @order.update(order_params) - send("validate_#{params[:step]}!") + + validate_current_step! @order.errors.empty? end + def summary_step? + params[:step] == "summary" + end + def advance_order_state return if @order.complete? OrderWorkflow.new(@order).advance_checkout(raw_params.slice(:shipping_method_id)) end + def validate_current_step! + step = ([params[:step]] & ["details", "payment", "summary"]).first + send("validate_#{step}!") + end + def validate_details! return true if params[:shipping_method_id].present? @@ -83,6 +87,7 @@ class SplitCheckoutController < ::BaseController def validate_summary! return true if params[:accept_terms] + return true unless TermsOfService.required?(@order.distributor) @order.errors.add(:terms_and_conditions, t("split_checkout.errors.terms_not_accepted")) end @@ -91,13 +96,7 @@ class SplitCheckoutController < ::BaseController @order_params ||= Checkout::Params.new(@order, params).call end - def redirect_to_guest - redirect_to checkout_step_path(:guest) - end - def redirect_to_step - return redirect_to_guest if !spree_current_user && !params[:step] - case @order.state when "cart", "address", "delivery" redirect_to checkout_step_path(:details) diff --git a/app/helpers/terms_and_conditions_helper.rb b/app/helpers/terms_and_conditions_helper.rb index ce53ac3ed9..c30b57164b 100644 --- a/app/helpers/terms_and_conditions_helper.rb +++ b/app/helpers/terms_and_conditions_helper.rb @@ -7,21 +7,25 @@ module TermsAndConditionsHelper end def render_terms_and_conditions - if platform_terms_required? && terms_and_conditions_activated? + if platform_terms_required? && distributor_terms_required? render("checkout/all_terms_and_conditions") elsif platform_terms_required? render "checkout/platform_terms_of_service" - elsif terms_and_conditions_activated? + elsif distributor_terms_required? render "checkout/terms_and_conditions" end end - def platform_terms_required? - Spree::Config.shoppers_require_tos + def any_terms_required?(distributor) + TermsOfService.required?(distributor) end - def terms_and_conditions_activated? - current_order.distributor.terms_and_conditions.file? + def platform_terms_required? + TermsOfService.platform_terms_required? + end + + def distributor_terms_required? + TermsOfService.distributor_terms_required?(current_order.distributor) end def all_terms_and_conditions_already_accepted? diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index ae82ac9a49..aa08cca513 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -30,7 +30,7 @@ module Spree go_to_state :complete end - attr_accessor :use_billing, :checkout_processing + attr_accessor :use_billing, :checkout_processing, :save_bill_address, :save_ship_address token_resource @@ -104,6 +104,8 @@ module Spree before_save :update_shipping_fees!, if: :complete? before_save :update_payment_fees!, if: :complete? + after_save_commit DefaultAddressUpdater + # -- Scopes scope :not_empty, -> { left_outer_joins(:line_items).where.not(spree_line_items: { id: nil }) diff --git a/app/services/checkout/params.rb b/app/services/checkout/params.rb index eaa0a0c815..10715c8acc 100644 --- a/app/services/checkout/params.rb +++ b/app/services/checkout/params.rb @@ -24,6 +24,7 @@ module Checkout def apply_strong_parameters @order_params = params.require(:order).permit( :email, :shipping_method_id, :special_instructions, :existing_card_id, + :save_bill_address, :save_ship_address, bill_address_attributes: ::PermittedAttributes::Address.attributes, ship_address_attributes: ::PermittedAttributes::Address.attributes, payments_attributes: [ diff --git a/app/services/default_address_updater.rb b/app/services/default_address_updater.rb new file mode 100644 index 0000000000..89868373d7 --- /dev/null +++ b/app/services/default_address_updater.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class DefaultAddressUpdater + def self.after_commit(order) + return unless order.save_bill_address || order.save_ship_address + + new(order).call + end + + def initialize(order) + @order = order + end + + def call + assign_bill_addresses + assign_ship_addresses + + customer.save + user&.save + end + + private + + attr_reader :order + + delegate :save_ship_address, :save_bill_address, :customer, :user, + :bill_address_id, :ship_address_id, to: :order + + def assign_bill_addresses + return unless save_bill_address + + customer.bill_address_id = bill_address_id + user&.bill_address_id = bill_address_id + end + + def assign_ship_addresses + return unless save_ship_address + + customer.ship_address_id = ship_address_id + user&.ship_address_id = ship_address_id + end +end diff --git a/app/services/terms_of_service.rb b/app/services/terms_of_service.rb index dc84b14988..95cbf661d6 100644 --- a/app/services/terms_of_service.rb +++ b/app/services/terms_of_service.rb @@ -10,4 +10,16 @@ class TermsOfService TermsOfServiceFile.updated_at end end + + def self.required?(distributor) + platform_terms_required? || distributor_terms_required?(distributor) + end + + def self.platform_terms_required? + Spree::Config.shoppers_require_tos + end + + def self.distributor_terms_required?(distributor) + distributor.terms_and_conditions.file? + end end diff --git a/app/views/split_checkout/_checkout.html.haml b/app/views/split_checkout/_checkout.html.haml index de3e5010c5..f26c8c4ca9 100644 --- a/app/views/split_checkout/_checkout.html.haml +++ b/app/views/split_checkout/_checkout.html.haml @@ -1,4 +1,4 @@ %checkout.row#checkout .small-12.medium-12.columns - = render partial: "split_checkout/tabs" unless checkout_step?(:guest) + = render partial: "split_checkout/tabs" = render partial: "split_checkout/form" diff --git a/app/views/split_checkout/_details.html.haml b/app/views/split_checkout/_details.html.haml index 4e0be37ac9..011e016fe7 100644 --- a/app/views/split_checkout/_details.html.haml +++ b/app/views/split_checkout/_details.html.haml @@ -60,8 +60,8 @@ - if spree_current_user||true %div.checkout-input - = f.check_box :checkout_default_bill_address - = f.label :checkout_default_bill_address, t(:checkout_default_bill_address) + = f.check_box :save_bill_address + = f.label :save_bill_address, t(:checkout_default_bill_address) %div.checkout-substep{ "data-controller": "toggle shippingmethod" } - selected_shipping_method = @order.shipping_method&.id || params[:shipping_method_id] @@ -136,8 +136,8 @@ - if spree_current_user %div.checkout-input{ "data-toggle-target": "content", style: "display: none" } - = f.check_box :default_ship_address, { id: "default_ship_address", name: "default_ship_address" } - = f.label :default_ship_address, t(:checkout_default_ship_address), { for: "default_ship_address" } + = f.check_box :save_ship_address + = f.label :save_ship_address, t(:checkout_default_ship_address) .div.checkout-input = f.label :special_instructions, t(:checkout_instructions) diff --git a/app/views/split_checkout/_guest.html.haml b/app/views/split_checkout/_guest.html.haml index f3f281a40b..61128fe3fe 100644 --- a/app/views/split_checkout/_guest.html.haml +++ b/app/views/split_checkout/_guest.html.haml @@ -1,12 +1,14 @@ -.medium-10 - %div.checkout-guest-title - = t :checkout_headline +.checkout-step + .medium-10 + %div.checkout-guest-title + = t :checkout_headline - %div.checkout-submit{ class: "#{@order.distributor.allow_guest_orders? ? 'checkout-submit-inline' : 'medium-6' }" } - %a.primary.button{href: main_app.login_path} - = t :label_login - -if @order.distributor.allow_guest_orders? - %span.checkout-submit-or + %div.checkout-submit{ class: "#{@order.distributor.allow_guest_orders? ? 'checkout-submit-inline' : 'medium-6' }" } + %button.button.primary{ "data-action": "click->guest-checkout#login" } + = t :label_login + -if @order.distributor.allow_guest_orders? + %span.checkout-submit-or + + %button.button.cancel{ "data-action": "click->guest-checkout#guestSelected" } + = t :checkout_as_guest - %a.button.cancel{href: main_app.checkout_step_path(:details)} - = t :checkout_as_guest diff --git a/app/views/split_checkout/_summary.html.haml b/app/views/split_checkout/_summary.html.haml index 57c9c18ab5..131f6f3e0e 100644 --- a/app/views/split_checkout/_summary.html.haml +++ b/app/views/split_checkout/_summary.html.haml @@ -71,15 +71,16 @@ = render 'spree/orders/summary', order: @order -%div.checkout-substep.medium-6 - %div.checkout-input - = f.check_box :accept_terms, { id: "accept_terms", name: "accept_terms", "checked": "#{all_terms_and_conditions_already_accepted?}" }, 1, nil - = f.label :accept_terms, t('split_checkout.step3.terms_and_conditions.message_html', terms_and_conditions_link: link_to( t("split_checkout.step3.terms_and_conditions.link_text"), @order.distributor.terms_and_conditions.url, target: '_blank'), tos_link: link_to_platform_terms), { for: "accept_terms" } +- if any_terms_required?(@order.distributor) + %div.checkout-substep.medium-6 + %div.checkout-input + = f.check_box :accept_terms, { id: "accept_terms", name: "accept_terms", "checked": "#{all_terms_and_conditions_already_accepted?}" }, 1, nil + = f.label :accept_terms, t('split_checkout.step3.terms_and_conditions.message_html', terms_and_conditions_link: link_to( t("split_checkout.step3.terms_and_conditions.link_text"), @order.distributor.terms_and_conditions.url, target: '_blank'), tos_link: link_to_platform_terms), { for: "accept_terms" } - = f.error_message_on :terms_and_conditions, standalone: true + = f.error_message_on :terms_and_conditions, standalone: true - %div.checkout-input - = t("split_checkout.step3.agree") + %div.checkout-input + = t("split_checkout.step3.agree") %div.checkout-submit.medium-6 = f.submit t("split_checkout.step3.submit"), name: "confirm_order", class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false diff --git a/app/views/split_checkout/edit.html.haml b/app/views/split_checkout/edit.html.haml index 282da26f2c..7043b31543 100644 --- a/app/views/split_checkout/edit.html.haml +++ b/app/views/split_checkout/edit.html.haml @@ -19,6 +19,12 @@ .sub-header.show-for-medium-down = render partial: "shopping_shared/order_cycles" - = render partial: "checkout" + .row{ "data-controller": "guest-checkout", "data-guest-checkout-distributor-value": @order.distributor.id } + %div{ style: "display: #{spree_current_user ? 'block' : 'none'}", "data-guest-checkout-target": "checkout" } + = render partial: "checkout" + + - unless spree_current_user + %div{ style: "display: block", "data-guest-checkout-target": "guest" } + = render partial: "guest" = render partial: "shared/footer" diff --git a/app/webpacker/controllers/guest_checkout_controller.js b/app/webpacker/controllers/guest_checkout_controller.js new file mode 100644 index 0000000000..450e6ca271 --- /dev/null +++ b/app/webpacker/controllers/guest_checkout_controller.js @@ -0,0 +1,35 @@ +import { Controller } from "stimulus" + +export default class extends Controller { + static targets = ["checkout", "guest"]; + static values = { + distributor: String, + session: { type: String, default: "guest-checkout" } + }; + + connect() { + if(!this.hasGuestTarget) { return } + + if(this.usingGuestCheckout()) { + this.showCheckout(); + } + } + + login() { + window.dispatchEvent(new Event("login:modal:open")) + } + + showCheckout() { + this.checkoutTarget.style.display = "block"; + this.guestTarget.style.display = "none"; + } + + guestSelected() { + this.showCheckout(); + sessionStorage.setItem(this.sessionValue, this.distributorValue); + } + + usingGuestCheckout() { + return sessionStorage.getItem(this.sessionValue) === this.distributorValue + } +} diff --git a/config/locales/de_DE.yml b/config/locales/de_DE.yml index cdf354c99d..153329b915 100644 --- a/config/locales/de_DE.yml +++ b/config/locales/de_DE.yml @@ -1353,6 +1353,7 @@ de_DE: terms_and_conditions: "Allgemeinen Geschäftsbedingungen (AGB)" failed: "Der Bestellabschluss ist fehlgeschlagen. Bitte geben Sie uns Bescheid, damit wir Ihre Bestellung dennoch bearbeiten können." payment_cancelled_due_to_stock: "Zahlung storniert: Der Bestellabschluss konnte aufgrund von Problemen mit dem Lagerbestand nicht abgeschlossen werden." + order_not_loaded: "Keine gültige Bestellung für den Bestellabschluss gefunden." shops: hubs: show_closed_shops: "Geschlossene Läden anzeigen" @@ -3215,7 +3216,6 @@ de_DE: notice_messages: variant_deleted: "Produktvariante wurde gelöscht" or: "oder" - order_processed_successfully: "Ihre Bestellung wurde erfolgreich verarbeitet." payment_method_not_supported: "Zahlungsart wird nicht unterstützt" resend_authorization_email: "Autorisierungs-E-Mail erneut senden" rma_credit: "Gutschrift aus Retour" diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index 4c79272083..31383df1e1 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -1351,6 +1351,7 @@ en_FR: terms_and_conditions: "Terms and Conditions" failed: "The checkout failed. Please let us know so that we can process your order." payment_cancelled_due_to_stock: "Payment cancelled: the checkout could not be completed due to stock issues." + order_not_loaded: "No valid order found for checkout processing" shops: hubs: show_closed_shops: "Show closed shops" @@ -3206,7 +3207,6 @@ en_FR: notice_messages: variant_deleted: "Variant deleted" or: "Or" - order_processed_successfully: "Order processed successfully" payment_method_not_supported: "Payment method not supported" resend_authorization_email: "Resend authorization email" rma_credit: "RMA credit" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 113f04ca30..e99d06fcdd 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1353,6 +1353,7 @@ fr: terms_and_conditions: "CGU & CGV" failed: "La validation du paiement a échoué. Contactez-nous afin de finaliser votre commande." payment_cancelled_due_to_stock: "Paiement annulé : la commande n'a pas été finalisée car un ou plusieurs articles ne sont plus en stock." + order_not_loaded: "Aucune commande n'a été trouvée pour poursuivre le parcours d'achat." shops: hubs: show_closed_shops: "Afficher les boutiques fermées" @@ -3235,7 +3236,6 @@ fr: notice_messages: variant_deleted: "Variante supprimée" or: "Ou" - order_processed_successfully: "Commande traitée avec succès" payment_method_not_supported: "Mode de paiement non pris en charge" resend_authorization_email: "Renvoyer l'e-mail d'autorisation" rma_credit: "Crédit ARM" diff --git a/config/locales/ru.yml b/config/locales/ru.yml index ee6ea40012..19c2ad6ea9 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -72,6 +72,44 @@ ru: missing: "У клиента нет карты, с которой начисляется оплата." processing_error: "Произошла ошибка при обработке карты." rate_limit: "Произошла ошибка из-за того, что запросы попали в API слишком быстро. Сообщите нам, если вы постоянно сталкиваетесь с этой ошибкой." + authentication_required: "Карта была отклонена, так как транзакция требует аутентификации." + approve_with_id: "Платеж не может быть авторизован." + call_issuer: "Карта была отклонена по неизвестной причине." + card_not_supported: "Карта не поддерживает этот тип покупки." + card_velocity_exceeded: "Клиент превысил баланс или кредитный лимит, доступный на его карте." + currency_not_supported: "Карта не поддерживает указанную валюту." + do_not_honor: "Карта была отклонена по неизвестной причине." + do_not_try_again: "Карта была отклонена по неизвестной причине." + duplicate_transaction: "Слишком часто была отправлена транзакция с одинаковой суммой и данными кредитной карты." + fraudulent: "Платеж был отклонен, так как Stripe подозревает, что это мошенничество." + generic_decline: "Карта была отклонена по неизвестной причине." + incorrect_pin: "Введен неверный PIN-код. Этот код отклонения применяется только к платежам, совершенным с помощью устройства для чтения карт." + insufficient_funds: "На карте недостаточно средств для совершения покупки." + invalid_account: "Карта или счет, к которому привязана карта, недействительны." + invalid_amount: "Сумма платежа недействительна или превышает допустимую сумму." + invalid_pin: "Введен неверный PIN-код. Этот код отклонения применяется только к платежам, совершенным с помощью устройства для чтения карт." + issuer_not_available: "Не удалось связаться с эмитентом карты, поэтому платеж не мог быть авторизован." + lost_card: "Платеж был отклонен, поскольку заявлено, что карта утеряна." + merchant_blacklist: "Платеж был отклонен, поскольку пользователь Stripe в черном списке." + new_account_information_available: "Карта или счет, к которому привязана карта, недействительны." + no_action_taken: "Карта была отклонена по неизвестной причине." + not_permitted: "Оплата не разрешена." + offline_pin_required: "Карта была отклонена, так как требует PIN-код." + online_or_offline_pin_required: "Карта была отклонена, так как требует PIN-код." + pickup_card: "Карту нельзя использовать для этого платежа (возможно, она была утеряна или украдена)." + pin_try_exceeded: "Превышено допустимое количество попыток ввода PIN-кода." + reenter_transaction: "Платеж не может быть обработан эмитентом по неизвестной причине." + restricted_card: "Карту нельзя использовать для этого платежа (возможно, она была утеряна или украдена)." + revocation_of_all_authorizations: "Карта была отклонена по неизвестной причине." + revocation_of_authorization: "Карта была отклонена по неизвестной причине." + security_violation: "Карта была отклонена по неизвестной причине." + service_not_allowed: "Карта была отклонена по неизвестной причине." + stolen_card: "Платеж был отклонен, поскольку карта была украдена." + stop_payment_order: "Карта была отклонена по неизвестной причине." + testmode_decline: "Был использован номер тестовой карты Stripe." + transaction_not_allowed: "Карта была отклонена по неизвестной причине." + try_again_later: "Карта была отклонена по неизвестной причине." + withdrawal_count_limit_exceeded: "Клиент превысил баланс или кредитный лимит, доступный на его карте." activemodel: attributes: order_management/reports/enterprise_fee_summary/parameters: @@ -138,6 +176,9 @@ ru: already_registered: "Этот адрес электронной почты уже зарегистрирован. Пожалуйста, войдите, чтобы продолжить, или вернитесь и используйте другой адрес электронной почты." success: logged_in_succesfully: "Вы успешно вошли" + sessions: + signed_out: "Выполнен выход." + already_signed_out: "Выполнен выход." user_passwords: spree_user: updated_not_active: "Ваш пароль был сброшен, но ваш адрес электронной почты еще не подтвержден." @@ -1313,6 +1354,7 @@ ru: terms_and_conditions: "Условия и Положения" failed: "Оформить заказ не удалось. Сообщите нам, чтобы мы могли обработать ваш заказ." payment_cancelled_due_to_stock: "Платеж отменен: оформление заказа не может быть выполнено из-за проблем на складе." + order_not_loaded: "Не найден действительный заказ для оформления" shops: hubs: show_closed_shops: "Показать закрытые магазины" @@ -3221,7 +3263,6 @@ ru: notice_messages: variant_deleted: "Вариант удален" or: "Или" - order_processed_successfully: "Заказ успешно обработан" payment_method_not_supported: "Способ оплаты не поддерживается" resend_authorization_email: "Повторно отправить письмо для авторизации" rma_credit: "Кредит RMA" diff --git a/config/routes.rb b/config/routes.rb index 0fa8f94a0a..d6a135b3dd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,7 +87,7 @@ Openfoodnetwork::Application.routes.draw do constraints SplitCheckoutConstraint.new do get '/checkout', to: 'split_checkout#edit' - constraints step: /(guest|details|payment|summary)/ do + constraints step: /(details|payment|summary)/ do get '/checkout/:step', to: 'split_checkout#edit', as: :checkout_step put '/checkout/:step', to: 'split_checkout#update', as: :checkout_update end diff --git a/spec/controllers/split_checkout_controller_spec.rb b/spec/controllers/split_checkout_controller_spec.rb new file mode 100644 index 0000000000..9c8963a349 --- /dev/null +++ b/spec/controllers/split_checkout_controller_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitCheckoutController, type: :controller do + let(:user) { order.user } + let(:address) { create(:address) } + let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + let(:order_cycle) { create(:order_cycle, distributors: [distributor]) } + let(:exchange) { order_cycle.exchanges.outgoing.first } + let(:order) { + create(:order_with_line_items, line_items_count: 1, distributor: distributor, + order_cycle: order_cycle) + } + let(:payment_method) { distributor.payment_methods.first } + let(:shipping_method) { distributor.shipping_methods.first } + + before do + allow(Flipper).to receive(:enabled?).with(:split_checkout) { true } + allow(Flipper).to receive(:enabled?).with(:split_checkout, anything) { true } + + exchange.variants << order.line_items.first.variant + allow(controller).to receive(:current_order) { order } + allow(controller).to receive(:spree_current_user) { user } + end + + describe "#edit" do + it "renders the checkout" do + get :edit, params: { step: "details" } + expect(response.status).to eq 200 + end + + it "redirects to current step if no step is given" do + get :edit + expect(response).to redirect_to checkout_step_path(:details) + end + + context "when line items in the cart are not valid" do + before { allow(controller).to receive(:valid_order_line_items?) { false } } + + it "redirects to cart" do + get :edit + expect(response).to redirect_to cart_path + end + end + end + + describe "#update" do + let(:checkout_params) { {} } + let(:params) { { step: step }.merge(checkout_params) } + + context "details step" do + let(:step) { "details" } + + context "with incomplete data" do + let(:checkout_params) { { order: { email: user.email } } } + + it "returns 422 and some feedback" do + put :update, params: params + + expect(response.status).to eq 422 + expect(flash[:error]).to eq "Saving failed, please update the highlighted fields." + expect(order.reload.state).to eq "cart" + end + end + + context "with complete data" do + let(:checkout_params) do + { + order: { + email: user.email, + bill_address_attributes: address.to_param, + ship_address_attributes: address.to_param + }, + shipping_method_id: shipping_method.id + } + end + + it "updates and redirects to payment step" do + put :update, params: params + + expect(response).to redirect_to checkout_step_path(:payment) + expect(order.reload.state).to eq "payment" + end + + describe "saving default addresses" do + it "updates default bill address on user and customer" do + put :update, params: params.merge({ order: { save_bill_address: true } }) + + expect(order.customer.bill_address).to eq(order.bill_address) + expect(order.user.bill_address).to eq(order.bill_address) + end + + it "updates default ship address on user and customer" do + put :update, params: params.merge({ order: { save_ship_address: true } }) + + expect(order.customer.ship_address).to eq(order.ship_address) + expect(order.user.ship_address).to eq(order.ship_address) + end + end + end + end + + context "payment step" do + let(:step) { "payment" } + + before do + order.bill_address = address + order.ship_address = address + order.select_shipping_method shipping_method.id + OrderWorkflow.new(order).advance_to_payment + end + + context "with incomplete data" do + let(:checkout_params) { { order: { email: user.email } } } + + it "returns 422 and some feedback" do + put :update, params: params + + expect(response.status).to eq 422 + expect(flash[:error]).to eq "Saving failed, please update the highlighted fields." + expect(order.reload.state).to eq "payment" + end + end + + context "with complete data" do + let(:checkout_params) do + { + order: { + payments_attributes: [ + { payment_method_id: payment_method.id } + ] + } + } + end + + it "updates and redirects to payment step" do + put :update, params: params + + expect(response).to redirect_to checkout_step_path(:summary) + expect(order.reload.state).to eq "confirmation" + end + end + end + + context "summary step" do + let(:step) { "summary" } + + before do + order.bill_address = address + order.ship_address = address + order.select_shipping_method shipping_method.id + OrderWorkflow.new(order).advance_to_payment + + order.payments << build(:payment, amount: order.total, payment_method: payment_method) + order.next + end + + describe "confirming the order" do + it "completes the order and redirects to order confirmation" do + put :update, params: params + + expect(response).to redirect_to order_path(order) + expect(order.reload.state).to eq "complete" + end + end + + context "when accepting T&Cs is required" do + before do + allow(TermsOfService).to receive(:platform_terms_required?) { true } + end + + describe "submitting without accepting the T&Cs" do + let(:checkout_params) { {} } + + it "returns 422 and some feedback" do + put :update, params: params + + expect(response.status).to eq 422 + expect(flash[:error]).to eq "Saving failed, please update the highlighted fields." + expect(order.reload.state).to eq "confirmation" + end + end + + describe "submitting and accepting the T&Cs" do + let(:checkout_params) { { accept_terms: true } } + + it "completes the order and redirects to order confirmation" do + put :update, params: params + + expect(response).to redirect_to order_path(order) + expect(order.reload.state).to eq "complete" + end + end + end + end + end +end diff --git a/spec/support/split_checkout_helper.rb b/spec/support/split_checkout_helper.rb index 1fb8a1fcec..7b6cafe8cf 100644 --- a/spec/support/split_checkout_helper.rb +++ b/spec/support/split_checkout_helper.rb @@ -14,34 +14,36 @@ module SplitCheckoutHelper end def fill_out_details - # Section: Your Details - within(:xpath, './/div[@class="checkout-substep"][1]') do - fill_in "First Name", with: "Will" - fill_in "Last Name", with: "Marshall" - fill_in "Email", with: "test@test.com" - fill_in "Phone", with: "0468363090" - end + fill_in "First Name", with: "Will" + fill_in "Last Name", with: "Marshall" + fill_in "Email", with: "test@test.com" + fill_in "Phone", with: "0468363090" end def fill_out_billing_address - # Section: Your Billing Address - within(:xpath, './/div[@class="checkout-substep"][2]') do - fill_in "Address", with: "Rue de la Vie, 77" - fill_in "City", with: "Melbourne" - fill_in "Postcode", with: "3066" - select "Australia", from: "Country" - select "Victoria", from: "State" - end + fill_in "order_bill_address_attributes_address1", with: "Rue de la Vie, 77" + fill_in "order_bill_address_attributes_address2", with: "2nd floor" + fill_in "order_bill_address_attributes_city", with: "Melbourne" + fill_in "order_bill_address_attributes_zipcode", with: "3066" + select "Australia", from: "order_bill_address_attributes_country_id" + select "Victoria", from: "order_bill_address_attributes_state_id" end def fill_out_shipping_address - # Section: Delivery Address - within(:xpath, './/div[@class="checkout-substep"][3]') do - fill_in "Address", with: "Rue de la Vie, 66" - fill_in "City", with: "Perth" - fill_in "Postcode", with: "2899" - select "Australia", from: "Country" - select "New South Wales", from: "State" - end + fill_in "order_ship_address_attributes_address1", with: "Rue de la Vie, 66" + fill_in "order_ship_address_attributes_address2", with: "3rd floor" + fill_in "order_ship_address_attributes_city", with: "Perth" + fill_in "order_ship_address_attributes_zipcode", with: "6603" + select "Australia", from: "order_ship_address_attributes_country_id" + select "New South Wales", from: "order_ship_address_attributes_state_id" + end + + def fill_notes(text) + fill_in 'Any comments or special instructions?', with: text.to_s + end + + def proceed_to_payment + click_button "Next - Payment method" + expect(page).to have_button("Next - Order summary") end end diff --git a/spec/system/consumer/split_checkout_spec.rb b/spec/system/consumer/split_checkout_spec.rb index 252fc97c74..670c17a951 100644 --- a/spec/system/consumer/split_checkout_spec.rb +++ b/spec/system/consumer/split_checkout_spec.rb @@ -53,27 +53,18 @@ describe "As a consumer, I want to checkout my order", js: true do context "guest checkout when distributor doesn't allow guest orders" do before do - visit checkout_path + visit checkout_step_path(:details) end it "should display the split checkout login page" do - expect(page).to have_content distributor.name - expect(page).to have_current_path("/checkout/guest") expect(page).to have_content("Ok, ready to checkout?") expect(page).to have_content("Login") expect(page).to have_no_content("Checkout as guest") end - it "should be redirected if user enter the url" do - order.update(state: "payment") - get checkout_step_path(:details) - expect(response).to have_http_status(:redirect) - expect(page).to have_current_path("/checkout/guest") - end - - it "should redirect to the login page when clicking the login button" do + it "should show the login modal when clicking the login button" do click_on "Login" - expect(page).to have_current_path "/" + expect(page).to have_selector ".login-modal" end end @@ -84,9 +75,8 @@ describe "As a consumer, I want to checkout my order", js: true do visit checkout_path end - it "should display the split checkout login/guest page" do + it "should display the split checkout login/guest form" do expect(page).to have_content distributor.name - expect(page).to have_current_path("/checkout/guest") expect(page).to have_content("Ok, ready to checkout?") expect(page).to have_content("Login") expect(page).to have_content("Checkout as guest") @@ -95,7 +85,6 @@ describe "As a consumer, I want to checkout my order", js: true do it "should display the split checkout details page" do click_on "Checkout as guest" expect(page).to have_content distributor.name - expect(page).to have_current_path("/checkout/details") expect(page).to have_content("1 - Your details") expect(page).to have_selector("div.checkout-tab.selected", text: "1 - Your details") expect(page).to have_content("2 - Payment method") @@ -122,7 +111,7 @@ describe "As a consumer, I want to checkout my order", js: true do choose free_shipping.name click_button "Next - Payment method" - expect(page).to have_current_path("/checkout/payment") + expect(page).to have_button("Next - Order summary") end context "when order is state: 'payment'" do @@ -154,24 +143,83 @@ describe "As a consumer, I want to checkout my order", js: true do end it "redirects the user to the Payment Method step" do - fill_in 'Any comments or special instructions?', with: "SpEcIaL NoTeS" - click_button "Next - Payment method" - expect(page).to have_current_path("/checkout/payment") + fill_notes("SpEcIaL NoTeS") + proceed_to_payment end end - describe "selecting a delivery shipping method and submiting the form" do + describe "selecting a delivery method" do before do choose shipping_with_fee.name - uncheck "ship_address_same_as_billing" end - it "redirects the user to the Payment Method step" do - fill_out_shipping_address - fill_in 'Any comments or special instructions?', with: "SpEcIaL NoTeS" - click_button "Next - Payment method" - expect(page).to have_current_path("/checkout/payment") + context "with same shipping and billing address" do + before do + check "ship_address_same_as_billing" + end + it "does not display the shipping address form" do + expect(page).not_to have_field "order_ship_address_attributes_address1" + end + + it "redirects the user to the Payment Method step, when submiting the form" do + proceed_to_payment + # asserts whether shipping and billing addresses are the same + ship_add_id = order.reload.ship_address_id + bill_add_id = order.reload.bill_address_id + expect(Spree::Address.where(id: bill_add_id).pluck(:address1) == + Spree::Address.where(id: ship_add_id).pluck(:address1)).to be true + end end + + context "with different shipping and billing address" do + before do + uncheck "ship_address_same_as_billing" + end + it "displays the shipping address form and the option to save it as default" do + expect(page).to have_field "order_ship_address_attributes_address1" + end + + it "displays error messages when submitting incomplete billing address" do + click_button "Next - Payment method" + expect(page).to have_content "Saving failed, please update the highlighted fields." + expect(page).to have_field("Address", with: "") + expect(page).to have_field("City", with: "") + expect(page).to have_field("Postcode", with: "") + expect(page).to have_content("can't be blank", count: 3) + end + + it "fills in shipping details and redirects the user to the Payment Method step, + when submiting the form" do + fill_out_shipping_address + fill_notes("SpEcIaL NoTeS") + proceed_to_payment + # asserts whether shipping and billing addresses are the same + ship_add_id = Spree::Order.first.ship_address_id + bill_add_id = Spree::Order.first.bill_address_id + expect(Spree::Address.where(id: bill_add_id).pluck(:address1) == + Spree::Address.where(id: ship_add_id).pluck(:address1)).to be false + end + end + end + end + + describe "not filling out delivery details" do + before do + fill_in "Email", with: "" + end + it "should display error when fields are empty" do + click_button "Next - Payment method" + expect(page).to have_content("Saving failed, please update the highlighted fields") + expect(page).to have_field("First Name", with: "") + expect(page).to have_field("Last Name", with: "") + expect(page).to have_field("Email", with: "") + expect(page).to have_content("is invalid") + expect(page).to have_field("Phone number", with: "") + expect(page).to have_field("Address", with: "") + expect(page).to have_field("City", with: "") + expect(page).to have_field("Postcode", with: "") + expect(page).to have_content("can't be blank", count: 7) + expect(page).to have_content("Select a shipping method") end end end