From 49f026629ca0a3a25b73dad5e27d0d91a1df41cf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 6 Jul 2021 11:17:02 +0200 Subject: [PATCH 01/22] Add tabs for the new split checkout + first step: your details - Add new split checkout for user with `split_checkout` feature - Add the first step: Your details - with 3 parts : Your details, Billing address and Delivery address --- .../stylesheets/darkswarm/branding.scss | 11 +- .../stylesheets/darkswarm/split-checkout.scss | 111 +++++++++++++++++ .../stylesheets/darkswarm/typography.scss | 2 +- app/views/checkout/edit.html.haml | 1 - app/views/split_checkout/_form.html.haml | 10 ++ app/views/split_checkout/_tabs.html.haml | 10 ++ .../split_checkout/_your_details.html.haml | 112 ++++++++++++++++++ app/views/split_checkout/edit.html.haml | 32 +++++ config/locales/en.yml | 45 +++++++ 9 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 app/assets/stylesheets/darkswarm/split-checkout.scss create mode 100644 app/views/split_checkout/_form.html.haml create mode 100644 app/views/split_checkout/_tabs.html.haml create mode 100644 app/views/split_checkout/_your_details.html.haml create mode 100644 app/views/split_checkout/edit.html.haml diff --git a/app/assets/stylesheets/darkswarm/branding.scss b/app/assets/stylesheets/darkswarm/branding.scss index 1ca4224cee..467ff9979a 100644 --- a/app/assets/stylesheets/darkswarm/branding.scss +++ b/app/assets/stylesheets/darkswarm/branding.scss @@ -1,6 +1,7 @@ $ofn-brand: #f27052; -$distributor-header-shadow: 0 1px 0 rgba(0, 0, 0, 0.05), 0 8px 6px -6px rgba(0, 0, 0, 0.2); +$distributor-header-shadow: 0 1px 0 rgba(0, 0, 0, 0.05), + 0 8px 6px -6px rgba(0, 0, 0, 0.2); // e.g. australia, uk, norway specific color @@ -36,8 +37,8 @@ $med-grey: #666; $med-drk-grey: #444; $dark-grey: #333; $light-grey: #ddd; -$light-grey-transparency: rgba(0, 0, 0, .1); -$very-light-grey-transparency: rgba(0, 0, 0, .05); +$light-grey-transparency: rgba(0, 0, 0, 0.1); +$very-light-grey-transparency: rgba(0, 0, 0, 0.05); $black: #000; $white: #fff; @@ -75,3 +76,7 @@ $social-facebook: #3b5998; $social-instagram: #e1306c; $social-linkedin: #0e76a8; $social-twitter: #00acee; + +// Typography +$min-accessible-grey: #666; +$darker-grey: #222; diff --git a/app/assets/stylesheets/darkswarm/split-checkout.scss b/app/assets/stylesheets/darkswarm/split-checkout.scss new file mode 100644 index 0000000000..a6a611e738 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/split-checkout.scss @@ -0,0 +1,111 @@ +.checkout-tab { + height: 4rem; + display: flex; + flex-direction: column; + justify-content: center; + + span { + font-size: 1.3rem; + @include headingFont; + } + + &:not(.selected) { + background-color: $white; + border-bottom: 5px solid $min-accessible-grey; + + span { + color: $min-accessible-grey; + } + } + + &.selected { + background-color: $ofn-brand; + + span { + color: $white; + text-decoration: underline; + } + } +} + +.checkout-step { + margin-right: auto; + margin-left: auto; + margin-top: 3rem; + + .checkout-substep { + margin-bottom: 1rem; + margin-top: 5rem; + + &:first-child { + margin-top: 3rem; + } + } + + .checkout-title { + font-size: 1.1rem; + @include headingFont; + font-weight: $font-weight-bold; + text-decoration: underline; + color: $darker-grey; + margin-bottom: 1.5rem; + } + + .checkout-input { + margin-bottom: 1.5rem; + + label { + margin-bottom: 0.3rem; + + &.error { + font-weight: $font-weight-bold; + color: $red-700; + } + } + + input, + select { + margin: 0; + + &.error { + border-color: $red-700; + } + } + + small.error { + background-color: rgba(193, 18, 43, 0.1); + color: $red-700; + font-style: normal; + margin: 0; + font-size: 0.8rem; + } + + #distributor_address.panel { + font-size: 0.875rem; + padding: 1rem; + + span { + white-space: pre-wrap; + } + } + } + + .checkout-submit { + margin-top: 5rem; + margin-bottom: 5rem; + + .button { + width: 100%; + margin-bottom: 2rem; + + &.primary { + background-color: $orange-500; + } + + &.cancel { + background-color: $grey-100; + color: $black; + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/typography.scss b/app/assets/stylesheets/darkswarm/typography.scss index f95de26a89..37d7c955c0 100644 --- a/app/assets/stylesheets/darkswarm/typography.scss +++ b/app/assets/stylesheets/darkswarm/typography.scss @@ -132,7 +132,7 @@ ul.check-list { } .light-grey { - color: #666666; + color: $min-accessible-grey; } .pad { diff --git a/app/views/checkout/edit.html.haml b/app/views/checkout/edit.html.haml index 9d85b3e8df..085542294a 100644 --- a/app/views/checkout/edit.html.haml +++ b/app/views/checkout/edit.html.haml @@ -33,5 +33,4 @@ .small-12.medium-4.columns = render partial: "checkout/summary" - = render partial: "shared/footer" diff --git a/app/views/split_checkout/_form.html.haml b/app/views/split_checkout/_form.html.haml new file mode 100644 index 0000000000..7c8ebf418d --- /dev/null +++ b/app/views/split_checkout/_form.html.haml @@ -0,0 +1,10 @@ +- content_for :injection_data do + = inject_available_shipping_methods + = inject_available_payment_methods + = inject_saved_credit_cards + += form_with url: update_checkout_path, model: @order, method: :put do |f| + + %div.checkout-step.medium-6 + - if true ## test step + = render "split_checkout/your_details", f: f diff --git a/app/views/split_checkout/_tabs.html.haml b/app/views/split_checkout/_tabs.html.haml new file mode 100644 index 0000000000..58097a7617 --- /dev/null +++ b/app/views/split_checkout/_tabs.html.haml @@ -0,0 +1,10 @@ +%div.flex + %div.columns.three.text-center.checkout-tab.selected + %span + = t("split_checkout.your_details") + %div.columns.three.text-center.checkout-tab + %span + = t("split_checkout.payment_method") + %div.columns.three.text-center.checkout-tab + %span + = t("split_checkout.order_summary") diff --git a/app/views/split_checkout/_your_details.html.haml b/app/views/split_checkout/_your_details.html.haml new file mode 100644 index 0000000000..2fd782c054 --- /dev/null +++ b/app/views/split_checkout/_your_details.html.haml @@ -0,0 +1,112 @@ +%div.checkout-substep + -# YOUR DETAILS + %div.checkout-title + = t("split_checkout.step1.your_details.title") + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :firstname, t("split_checkout.step1.your_details.first_name.label") + = bill_address.text_field :firstname, { placeholder: t("split_checkout.step1.your_details.first_name.placeholder") } + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :lastname, t("split_checkout.step1.your_details.last_name.label") + = bill_address.text_field :lastname, { placeholder: t("split_checkout.step1.your_details.last_name.placeholder") } + + %div.checkout-input + = f.label :email, t("split_checkout.step1.your_details.email.label") + = f.text_field :email, { placeholder: t("split_checkout.step1.your_details.email.placeholder") } + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :phone, t("split_checkout.step1.your_details.phone.label") + = bill_address.text_field :phone, { placeholder: t("split_checkout.step1.your_details.phone.placeholder") } + +%div.checkout-substep + -# BILLING ADDRESS + %div.checkout-title + = t("split_checkout.step1.billing_address.title") + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :address1, t("split_checkout.step1.billing_address.address1.label") + = bill_address.text_field :address1, { placeholder: t("split_checkout.step1.billing_address.address1.placeholder") } + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = f.label :address2, t("split_checkout.step1.billing_address.address2.label") + = f.text_field :address2, { placeholder: t("split_checkout.step1.billing_address.address2.placeholder") } + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :city, t("split_checkout.step1.billing_address.city.label") + = bill_address.text_field :city, { placeholder: t("split_checkout.step1.billing_address.city.placeholder") } + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :state_id, t("split_checkout.step1.billing_address.state_id.label") + -# todo: get state // values: s.id as s.name for s in countriesById[order.bill_address.country_id].states // defaultvalue: {} + = bill_address.select :state_id, [] + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :zipcode, t("split_checkout.step1.billing_address.zipcode.label") + = bill_address.text_field :zipcode, { placeholder: t("split_checkout.step1.billing_address.zipcode.placeholder") } + + %div.checkout-input + = fields_for :bill_address, @order.bill_address do |bill_address| + = bill_address.label :country_id, t("split_checkout.step1.billing_address.country_id.label") + -# todo: get countries // values: c.id as c.name for c in countries // defaultvalue: order.bill_address.country_id = order.bill_address.country_id || #{DefaultCountry.id} + = bill_address.select :country_id, [] + + - 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) + + %div.checkout-substep + -# DELIVERY ADDRESS + %div.checkout-title + = t("split_checkout.step1.delivery_address.title") + + = for shipping_method in @shipping_methods + %div.checkout-input + = fields_for shipping_method do |shipping_method_form| + = shipping_method_form.check_box :name, {id: "shipping_method_" + shipping_method.id.to_s } + = shipping_method_form.label :name, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s } + %em.light + -# todo: display method price | localizeCurrency // How to retrive shipping method price? + = if shipping_method.price && shipping_method.price != 0 + = method.price + - else + = t(:checkout_method_free) + + + - if true # todo: Checkout.requireShipAddress() // requireShipAddress: -> @shippingMethod()?.require_ship_address + %div.checkout-input + = f.check_box "Checkout.ship_address_same_as_billing", { id: "Checkout.ship_address_same_as_billing" } # "ng-model": "Checkout.ship_address_same_as_billing"}s + = f.label "Checkout.ship_address_same_as_billing", t(:checkout_address_same), { for: "Checkout.ship_address_same_as_billing" } + + - if spree_current_user || true # todo: && Checkout.requireShipAddress() // requireShipAddress: -> @shippingMethod()?.require_ship_address + %div.checkout-input + = f.check_box "Checkout.default_ship_address", { id: "Checkout.default_ship_address" } + = f.label "Checkout.default_ship_address", t(:checkout_default_ship_address), { for: "Checkout.default_ship_address" } + + - if true # todo: Checkout.shippingMethod().description + .div.checkout-input + #distributor_address.panel + %span{}{{ Checkout.shippingMethod().description }} + %br/ + %br/ + - if @order.order_cycle.pickup_time_for(@order.distributor) + = t :checkout_ready_for + = @order.order_cycle.pickup_time_for(@order.distributor) + + .div.checkout-input + = f.label :special_instructions, t(:checkout_instructions) + = f.text_area :special_instructions, size: "60x4" + + %div.checkout-submit + = f.submit t("split_checkout.step1.submit"), class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false + %a.button.cancel{href: main_app.cart_path} + = t("split_checkout.step1.cancel") diff --git a/app/views/split_checkout/edit.html.haml b/app/views/split_checkout/edit.html.haml new file mode 100644 index 0000000000..d56295b53e --- /dev/null +++ b/app/views/split_checkout/edit.html.haml @@ -0,0 +1,32 @@ +- content_for(:title) do + = t :checkout_title + +- content_for :injection_data do + = inject_enterprise_and_relatives + = inject_available_countries + +.darkswarm.footer-pad + - content_for :order_cycle_form do + %closing + = t :checkout_now + %p + = t :checkout_order_ready + %strong + = pickup_time current_order_cycle + + - content_for :ordercycle_sidebar do + .show-for-large-up.large-4.columns + = render partial: "shopping_shared/order_cycles" + + = render partial: "shopping_shared/header" + + .sub-header.show-for-medium-down + = render partial: "shopping_shared/order_cycles" + + %checkout.row + .small-12.medium-12.columns + = render partial: "split_checkout/tabs" + = render partial: "split_checkout/form" + + += render partial: "shared/footer" diff --git a/config/locales/en.yml b/config/locales/en.yml index d20f7e7c7e..77abd79bcb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1620,6 +1620,51 @@ en: checkout_back_to_cart: "Back to Cart" cost_currency: "Cost Currency" + split_checkout: + your_details: 1 - Your details + payment_method: 2 - Payment method + order_summary: 3 - Order summary + step1: + your_details: + title: Your details + first_name: + label: First Name + placeholder: e.g. Jane + last_name: + label: Last Name + placeholder: e.g. Doe + email: + label: Email + placeholder: e.g. Janedoe@email.com + phone: + label: Phone number + placeholder: e.g. 07987654321 + billing_address: + title: Billing address + address1: + label: Address (Street + House Number) + placeholder: e.g. Flat 1 Elm apartments + address2: + label: Additional address info (optional) + placeholder: e.g. Cavalier avenur + city: + label: City + placeholder: e.g. London + state_id: + label: State + zipcode: + label: Postcode + placeholder: e.g. SW11 3QN + country_id: + label: Country + delivery_address: + title: Delivery address + submit: Next - Payment method + cancel: Back to Edit basket + errors: + required: Field cannot be blank + invalid_number: "Please enter a valid phone number" + invalid_email: "Please enter a valid email address" order_paid: PAID order_not_paid: NOT PAID order_total: Total order From af79969d96a4c16a754df023f6d41c6ae753686b Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Sun, 4 Jul 2021 09:40:57 -0700 Subject: [PATCH 02/22] add SplitCheckoutController and conditional route --- app/constraints/split_checkout_constraint.rb | 9 +++++++++ app/controllers/split_checkout_controller.rb | 3 +++ config/routes.rb | 15 +++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 app/constraints/split_checkout_constraint.rb create mode 100644 app/controllers/split_checkout_controller.rb diff --git a/app/constraints/split_checkout_constraint.rb b/app/constraints/split_checkout_constraint.rb new file mode 100644 index 0000000000..e0b0a18eda --- /dev/null +++ b/app/constraints/split_checkout_constraint.rb @@ -0,0 +1,9 @@ +class SplitCheckoutConstraint + def matches?(request) + Flipper.enabled? :split_checkout, current_user(request) + end + + def current_user(request) + @spree_current_user ||= request.env['warden'].user + end +end diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb new file mode 100644 index 0000000000..4067ad5060 --- /dev/null +++ b/app/controllers/split_checkout_controller.rb @@ -0,0 +1,3 @@ +class SplitCheckoutController < CheckoutController + +end diff --git a/config/routes.rb b/config/routes.rb index 3385d97a02..1e44f533d0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,10 +69,17 @@ Openfoodnetwork::Application.routes.draw do resources :webhooks, only: [:create] end - get '/checkout', to: 'checkout#edit' , as: :checkout - put '/checkout', to: 'checkout#update' , as: :update_checkout - get '/checkout/:state', to: 'checkout#edit', as: :checkout_state - get '/checkout/paypal_payment/:order_id', to: 'checkout#paypal_payment', as: :paypal_payment + constraints(SplitCheckoutConstraint.new) do + get '/checkout', to: 'split_checkout#edit', as: :checkout + put '/checkout', to: 'split_checkout#update', as: :update_checkout + get '/checkout/:state', to: 'split_checkout#edit', as: :checkout_state + get '/checkout/paypal_payment/:order_id', to: 'split_checkout#paypal_payment', as: :paypal_payment + end + + get '/checkout', to: 'checkout#edit' + put '/checkout', to: 'checkout#update' + get '/checkout/:state', to: 'checkout#edit' + get '/checkout/paypal_payment/:order_id', to: 'checkout#paypal_payment' get 'embedded_shopfront/shopfront_session', to: 'application#shopfront_session' post 'embedded_shopfront/enable', to: 'application#enable_embedded_styles' From 87a0a5846d254d06afff5d802d533acf70d5fc0c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 6 Jul 2021 11:18:13 +0200 Subject: [PATCH 03/22] WIP; only advance order to delivery state Use the hidden_field_tag rails helper and move it --- app/controllers/checkout_controller.rb | 6 ++--- app/controllers/split_checkout_controller.rb | 23 ++++++++++++++++++++ app/models/spree/payment.rb | 2 +- app/views/split_checkout/_form.html.haml | 1 + 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 547f180a72..d7bbb2ce42 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -46,7 +46,7 @@ class CheckoutController < ::BaseController params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) return action_failed unless @order.update(params_adapter.params[:order] || {}) - checkout_workflow(params_adapter.shipping_method_id) + checkout_workflow(params_adapter.shipping_method_id, params[:advance_to_state]) rescue Spree::Core::GatewayError => e rescue_from_spree_gateway_error(e) rescue StandardError => e @@ -167,8 +167,8 @@ class CheckoutController < ::BaseController end end - def checkout_workflow(shipping_method_id) - while @order.state != "complete" + def checkout_workflow(shipping_method_id, advance_to_state = "complete") + while @order.state != advance_to_state if @order.state == "payment" return if redirect_to_payment_gateway diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 4067ad5060..48f48b8f84 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -1,3 +1,26 @@ class SplitCheckoutController < CheckoutController + def update + byebug + params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) + return action_failed unless @order.update(params_adapter.params[:order] || {}) + checkout_workflow(params_adapter.shipping_method_id, params[:advance_to_state] || "delivery") + rescue Spree::Core::GatewayError => e + byebug + rescue_from_spree_gateway_error(e) + rescue StandardError => e + byebug + flash[:error] = I18n.t("checkout.failed") + action_failed(e) + ensure + @order.update_order! + end + + private + + def redirect_to_payment_gateway + return unless params&.dig(:order)&.dig(:payments_attributes)&.first&.dig(:payments_attributes) + + super + end end diff --git a/app/models/spree/payment.rb b/app/models/spree/payment.rb index d807d6fac1..915ada2f2e 100644 --- a/app/models/spree/payment.rb +++ b/app/models/spree/payment.rb @@ -145,7 +145,7 @@ module Spree adjustment.originator = payment_method adjustment.label = adjustment_label adjustment.save - else + elsif payment_method.present? payment_method.create_adjustment(adjustment_label, self, true) adjustment.reload end diff --git a/app/views/split_checkout/_form.html.haml b/app/views/split_checkout/_form.html.haml index 7c8ebf418d..e71e87677c 100644 --- a/app/views/split_checkout/_form.html.haml +++ b/app/views/split_checkout/_form.html.haml @@ -7,4 +7,5 @@ %div.checkout-step.medium-6 - if true ## test step + = hidden_field_tag "advance_to_state", "delivery" = render "split_checkout/your_details", f: f From 0ea968a9004016d85c36ec411c55119c839577cd Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Sat, 17 Jul 2021 15:09:32 -0700 Subject: [PATCH 04/22] add redirection to correct step --- app/controllers/split_checkout_controller.rb | 24 +++++++++++++++++--- config/routes.rb | 3 +++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 48f48b8f84..aef3a1fdfe 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -1,15 +1,17 @@ class SplitCheckoutController < CheckoutController + def edit + redirect_to_step if request.path == "/checkout" + super + end + def update - byebug params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) return action_failed unless @order.update(params_adapter.params[:order] || {}) checkout_workflow(params_adapter.shipping_method_id, params[:advance_to_state] || "delivery") rescue Spree::Core::GatewayError => e - byebug rescue_from_spree_gateway_error(e) rescue StandardError => e - byebug flash[:error] = I18n.t("checkout.failed") action_failed(e) ensure @@ -18,9 +20,25 @@ class SplitCheckoutController < CheckoutController private + def redirect_to_step + if @order.state = "payment" + if @order.payment_method_id.nil? + redirect_to checkout_payment_method_path + else + redirect_to checkout_order_summary_path + end + elsif @order.state == "cart" + redirect_to checkout_your_details_path + end + end + def redirect_to_payment_gateway return unless params&.dig(:order)&.dig(:payments_attributes)&.first&.dig(:payments_attributes) super end + + def update_response + redirect_to checkout_payment_method_path + end end diff --git a/config/routes.rb b/config/routes.rb index 1e44f533d0..c0f68a28a9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -72,6 +72,9 @@ Openfoodnetwork::Application.routes.draw do constraints(SplitCheckoutConstraint.new) do get '/checkout', to: 'split_checkout#edit', as: :checkout put '/checkout', to: 'split_checkout#update', as: :update_checkout + get '/checkout/enter-your-details', to: 'split_checkout#edit', as: :checkout_your_details + get '/checkout/payment-method', to: 'split_checkout#edit', as: :checkout_payment_method + get '/checkout/order-summary', to: 'split_checkout#edit', as: :checkout_summary get '/checkout/:state', to: 'split_checkout#edit', as: :checkout_state get '/checkout/paypal_payment/:order_id', to: 'split_checkout#paypal_payment', as: :paypal_payment end From 6866cecdfa6a34cad630005e7de104ac88d2deb2 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Mon, 19 Jul 2021 20:44:03 -0700 Subject: [PATCH 05/22] start with a straight copy of CheckoutController - WIP! --- app/controllers/checkout_controller.rb | 6 +- app/controllers/split_checkout_controller.rb | 240 ++++++++++++++++++- 2 files changed, 236 insertions(+), 10 deletions(-) diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index d7bbb2ce42..547f180a72 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -46,7 +46,7 @@ class CheckoutController < ::BaseController params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) return action_failed unless @order.update(params_adapter.params[:order] || {}) - checkout_workflow(params_adapter.shipping_method_id, params[:advance_to_state]) + checkout_workflow(params_adapter.shipping_method_id) rescue Spree::Core::GatewayError => e rescue_from_spree_gateway_error(e) rescue StandardError => e @@ -167,8 +167,8 @@ class CheckoutController < ::BaseController end end - def checkout_workflow(shipping_method_id, advance_to_state = "complete") - while @order.state != advance_to_state + def checkout_workflow(shipping_method_id) + while @order.state != "complete" if @order.state == "payment" return if redirect_to_payment_gateway diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index aef3a1fdfe..0dec1b5951 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -1,14 +1,53 @@ -class SplitCheckoutController < CheckoutController +# frozen_string_literal: true + +require 'open_food_network/address_finder' + +class SplitCheckoutController < ::BaseController + layout 'darkswarm' + + include OrderStockCheck + + helper 'terms_and_conditions' + helper 'checkout' + + # We need pessimistic locking to avoid race conditions. + # Otherwise we fail on duplicate indexes or end up with negative stock. + prepend_around_action CurrentOrderLocker, only: [:edit, :update] + + prepend_before_action :check_hub_ready_for_checkout + prepend_before_action :check_order_cycle_expiry + prepend_before_action :require_order_cycle + prepend_before_action :require_distributor_chosen + + before_action :load_order + + before_action :ensure_order_not_completed + before_action :ensure_checkout_allowed + before_action :handle_insufficient_stock + + before_action :associate_user + before_action :check_authorization + before_action :enable_embedded_shopfront + + helper 'spree/orders' + def edit redirect_to_step if request.path == "/checkout" - super + return handle_redirect_from_stripe if valid_payment_intent_provided? + + # This is only required because of spree_paypal_express. If we implement + # a version of paypal that uses this controller, and more specifically + # the #action_failed method, then we can remove this call + # OrderCheckoutRestart.new(@order).call + rescue Spree::Core::GatewayError => e + rescue_from_spree_gateway_error(e) end def update params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) return action_failed unless @order.update(params_adapter.params[:order] || {}) - checkout_workflow(params_adapter.shipping_method_id, params[:advance_to_state] || "delivery") + checkout_workflow(params_adapter.shipping_method_id) rescue Spree::Core::GatewayError => e rescue_from_spree_gateway_error(e) rescue StandardError => e @@ -18,27 +57,214 @@ class SplitCheckoutController < CheckoutController @order.update_order! end + # Clears the cached order. Required for #current_order to return a new order + # to serve as cart. See https://github.com/spree/spree/blob/1-3-stable/core/lib/spree/core/controller_helpers/order.rb#L14 + # for details. + def expire_current_order + session[:order_id] = nil + @current_order = nil + end + private def redirect_to_step if @order.state = "payment" - if @order.payment_method_id.nil? + if true#@order.payment_method_id.nil? redirect_to checkout_payment_method_path else - redirect_to checkout_order_summary_path + redirect_to checkout_order_summary_path end elsif @order.state == "cart" redirect_to checkout_your_details_path end end + def check_authorization + authorize!(:edit, current_order, session[:access_token]) + end + + def ensure_checkout_allowed + redirect_to main_app.cart_path unless @order.checkout_allowed? + end + + def ensure_order_not_completed + redirect_to main_app.cart_path if @order.completed? + end + + def load_order + @order = current_order + + redirect_to(main_app.shop_path) && return if redirect_to_shop? + redirect_to_cart_path && return unless valid_order_line_items? + + before_address + setup_for_current_state + end + + def redirect_to_shop? + !@order || + !@order.checkout_allowed? || + @order.completed? + end + + def valid_order_line_items? + @order.insufficient_stock_lines.empty? && + OrderCycleDistributedVariants.new(@order.order_cycle, @order.distributor). + distributes_order_variants?(@order) + end + + def redirect_to_cart_path + respond_to do |format| + format.html do + redirect_to main_app.cart_path + end + + format.json do + render json: { path: main_app.cart_path }, status: :bad_request + end + end + end + + def setup_for_current_state + method_name = :"before_#{@order.state}" + __send__(method_name) if respond_to?(method_name, true) + end + + def before_address + associate_user + + finder = OpenFoodNetwork::AddressFinder.new(@order.email, @order.customer, spree_current_user) + + @order.bill_address = finder.bill_address + @order.ship_address = finder.ship_address + end + + def before_payment + current_order.payments.destroy_all if request.put? + end + + def valid_payment_intent_provided? + return false unless params["payment_intent"]&.starts_with?("pi_") + + last_payment = OrderPaymentFinder.new(@order).last_payment + @order.state == "payment" && + last_payment&.state == "requires_authorization" && + last_payment&.response_code == params["payment_intent"] + end + + def handle_redirect_from_stripe + return checkout_failed unless @order.process_payments! + + if OrderWorkflow.new(@order).next && order_complete? + checkout_succeeded + redirect_to(order_path(@order)) && return + else + checkout_failed + end + end + + def checkout_workflow(shipping_method_id, advance_to_state = "complete") + while @order.state != advance_to_state + if @order.state == "payment" + return if redirect_to_payment_gateway + + return action_failed unless @order.process_payments! + end + + next if OrderWorkflow.new(@order).next({ shipping_method_id: shipping_method_id }) + + return action_failed + end + + update_response + end + def redirect_to_payment_gateway return unless params&.dig(:order)&.dig(:payments_attributes)&.first&.dig(:payments_attributes) - super + redirect_path = Checkout::PaypalRedirect.new(params).path + redirect_path = Checkout::StripeRedirect.new(params, @order).path if redirect_path.blank? + return if redirect_path.blank? + + render json: { path: redirect_path }, status: :ok + true + end + + def order_error + if @order.errors.present? + @order.errors.full_messages.to_sentence + else + t(:payment_processing_failed) + end end def update_response - redirect_to checkout_payment_method_path + if order_complete? + checkout_succeeded + update_succeeded_response + else + action_failed(RuntimeError.new("Order not complete after the checkout workflow")) + end + end + + def order_complete? + @order.state == "complete" || @order.completed? + end + + def checkout_succeeded + Checkout::PostCheckoutActions.new(@order).success(self, params, spree_current_user) + + session[:access_token] = current_order.token + flash[:notice] = t(:order_processed_successfully) + end + + def update_succeeded_response + respond_to do |format| + format.html do + respond_with(@order, location: order_path(@order)) + end + format.json do + render json: { path: order_path(@order) }, status: :ok + end + end + end + + def action_failed(error = RuntimeError.new(order_error)) + checkout_failed(error) + action_failed_response + end + + def checkout_failed(error = RuntimeError.new(order_error)) + Bugsnag.notify(error) + flash[:error] = order_error if flash.blank? + Checkout::PostCheckoutActions.new(@order).failure + end + + def action_failed_response + respond_to do |format| + format.html do + render :edit + end + format.json do + discard_flash_errors + render json: { errors: @order.errors, flash: flash.to_hash }.to_json, status: :bad_request + end + end + end + + def rescue_from_spree_gateway_error(error) + flash[:error] = t(:spree_gateway_error_flash_for_checkout, error: error.message) + action_failed(error) + end + + def permitted_params + PermittedAttributes::Checkout.new(params).call + end + + def discard_flash_errors + # Marks flash errors for deletion after the current action has completed. + # This ensures flash errors generated during XHR requests are not persisted in the + # session for longer than expected. + flash.discard(:error) end end From 1076ec777c0d32b5a798f17be30037f4a181661e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 20 Jul 2021 17:44:41 +0200 Subject: [PATCH 06/22] Define a method error_message_on to display error by field --- .../stylesheets/darkswarm/split-checkout.scss | 24 ++++++++++++------- app/helpers/application_helper.rb | 11 +++++++++ .../split_checkout/_your_details.html.haml | 16 +++++++++---- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/split-checkout.scss b/app/assets/stylesheets/darkswarm/split-checkout.scss index a6a611e738..776cb1ff29 100644 --- a/app/assets/stylesheets/darkswarm/split-checkout.scss +++ b/app/assets/stylesheets/darkswarm/split-checkout.scss @@ -54,30 +54,36 @@ .checkout-input { margin-bottom: 1.5rem; - label { - margin-bottom: 0.3rem; - - &.error { + .field_with_errors { + label { font-weight: $font-weight-bold; color: $red-700; } + input { + border-color: $red-700; + } + } + + label { + margin-bottom: 0.3rem; } input, select { margin: 0; - - &.error { - border-color: $red-700; - } } - small.error { + span.formError { background-color: rgba(193, 18, 43, 0.1); color: $red-700; font-style: normal; margin: 0; font-size: 0.8rem; + display: block; + padding-left: 5px; + padding-right: 5px; + padding-top: 2px; + padding-bottom: 2px; } #distributor_address.panel { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 83c19dc94a..c00825f74d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -4,6 +4,17 @@ module ApplicationHelper include RawParams include Pagy::Frontend + def error_message_on(object, method, _options = {}) + object = convert_to_model(object) + obj = object.respond_to?(:errors) ? object : instance_variable_get("@#{object}") + if obj && obj.errors[method].present? + errors = obj.errors[method].map { |err| h(err) }.join('
').html_safe + content_tag(:span, errors, class: 'formError') + else + '' + end + end + def feature?(feature, user = nil) OpenFoodNetwork::FeatureToggle.enabled?(feature, user) end diff --git a/app/views/split_checkout/_your_details.html.haml b/app/views/split_checkout/_your_details.html.haml index 2fd782c054..ff395fa334 100644 --- a/app/views/split_checkout/_your_details.html.haml +++ b/app/views/split_checkout/_your_details.html.haml @@ -7,20 +7,24 @@ = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :firstname, t("split_checkout.step1.your_details.first_name.label") = bill_address.text_field :firstname, { placeholder: t("split_checkout.step1.your_details.first_name.placeholder") } + = f.error_message_on "bill_address.firstname" %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :lastname, t("split_checkout.step1.your_details.last_name.label") = bill_address.text_field :lastname, { placeholder: t("split_checkout.step1.your_details.last_name.placeholder") } + = f.error_message_on "bill_address.lastname" %div.checkout-input = f.label :email, t("split_checkout.step1.your_details.email.label") = f.text_field :email, { placeholder: t("split_checkout.step1.your_details.email.placeholder") } - + = f.error_message_on :email + %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :phone, t("split_checkout.step1.your_details.phone.label") = bill_address.text_field :phone, { placeholder: t("split_checkout.step1.your_details.phone.placeholder") } + = f.error_message_on "bill_address.phone" %div.checkout-substep -# BILLING ADDRESS @@ -31,27 +35,31 @@ = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :address1, t("split_checkout.step1.billing_address.address1.label") = bill_address.text_field :address1, { placeholder: t("split_checkout.step1.billing_address.address1.placeholder") } + = f.error_message_on "bill_address.address1" %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = f.label :address2, t("split_checkout.step1.billing_address.address2.label") = f.text_field :address2, { placeholder: t("split_checkout.step1.billing_address.address2.placeholder") } + = f.error_message_on "bill_address.address2" %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :city, t("split_checkout.step1.billing_address.city.label") = bill_address.text_field :city, { placeholder: t("split_checkout.step1.billing_address.city.placeholder") } + = f.error_message_on "bill_address.city" %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :state_id, t("split_checkout.step1.billing_address.state_id.label") -# todo: get state // values: s.id as s.name for s in countriesById[order.bill_address.country_id].states // defaultvalue: {} - = bill_address.select :state_id, [] + = bill_address.select :state_id, [] %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :zipcode, t("split_checkout.step1.billing_address.zipcode.label") = bill_address.text_field :zipcode, { placeholder: t("split_checkout.step1.billing_address.zipcode.placeholder") } + = f.error_message_on "bill_address.zipcode" %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| @@ -69,10 +77,10 @@ %div.checkout-title = t("split_checkout.step1.delivery_address.title") - = for shipping_method in @shipping_methods + = @shipping_methods.each do |shipping_method| %div.checkout-input = fields_for shipping_method do |shipping_method_form| - = shipping_method_form.check_box :name, {id: "shipping_method_" + shipping_method.id.to_s } + = shipping_method_form.check_box :name, {id: "shipping_method_" + shipping_method.id.to_s }, shipping_method.id, 0 = shipping_method_form.label :name, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s } %em.light -# todo: display method price | localizeCurrency // How to retrive shipping method price? From 200ced62fb02d119f1bc49dacef6a1986e2faf76 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 20 Jul 2021 15:46:15 +0200 Subject: [PATCH 07/22] Load shipping methods in the Controller needed to populate the form --- app/controllers/split_checkout_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 0dec1b5951..483c99834a 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -19,7 +19,7 @@ class SplitCheckoutController < ::BaseController prepend_before_action :require_order_cycle prepend_before_action :require_distributor_chosen - before_action :load_order + before_action :load_order, :load_shipping_methods before_action :ensure_order_not_completed before_action :ensure_checkout_allowed @@ -91,6 +91,10 @@ class SplitCheckoutController < ::BaseController redirect_to main_app.cart_path if @order.completed? end + def load_shipping_methods + @shipping_methods = Spree::ShippingMethod.for_distributor(@order.distributor).order(:name) + end + def load_order @order = current_order From 151a6fd16bea2eaebadd94f11566bbf52a9bd6a5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 26 Jul 2021 10:43:27 +0200 Subject: [PATCH 08/22] Add first stimulus controllers to display elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Introduce a stimulus toggle_controller - controller: { "data-controller": "toggle" } - action: { "data-action": "toggle#toggle" } - show or not: { "data-toggle-show": true || false  } - targets: { "data-toggle-target": "content", style: "display: none" } Display payment method price 2. States are populated via a new dependant_select_controller by stimulus. Usage: - controller : { "data-controller": "dependant-select", "data-dependant-select-options-value": [ [1: ["option", "for", "1"], [2: ["option", "for", "2"] ] } - target (on the populating target): { "data-dependant-select-target": "select" } - source and action (on the input that leads the dependant select): {"data-dependant-select-target": "source", "data-action": "dependant-select#handleSelectChange"} Some improvements on readability 3. Populate ShippingMethod description thanks to "shippingmethod_controller" + - Add countries and states --- app/controllers/split_checkout_controller.rb | 8 ++- .../split_checkout/_your_details.html.haml | 56 +++++++++---------- .../dependant_select_controller.js | 27 +++++++++ .../controllers/shippingmethod_controller.js | 24 ++++++++ .../controllers/toggle_controller.js | 12 ++++ 5 files changed, 96 insertions(+), 31 deletions(-) create mode 100644 app/webpacker/controllers/dependant_select_controller.js create mode 100644 app/webpacker/controllers/shippingmethod_controller.js create mode 100644 app/webpacker/controllers/toggle_controller.js diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 483c99834a..1134fcced7 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -6,6 +6,7 @@ class SplitCheckoutController < ::BaseController layout 'darkswarm' include OrderStockCheck + include Spree::BaseHelper helper 'terms_and_conditions' helper 'checkout' @@ -19,7 +20,7 @@ class SplitCheckoutController < ::BaseController prepend_before_action :require_order_cycle prepend_before_action :require_distributor_chosen - before_action :load_order, :load_shipping_methods + before_action :load_order, :load_shipping_methods, :load_countries before_action :ensure_order_not_completed before_action :ensure_checkout_allowed @@ -95,6 +96,11 @@ class SplitCheckoutController < ::BaseController @shipping_methods = Spree::ShippingMethod.for_distributor(@order.distributor).order(:name) end + def load_countries + @countries = available_countries.map { |c| [c.name, c.id] } + @countries_with_states = available_countries.map { |c| [c.id, c.states.map { |s| [s.name, s.id] }] } + end + def load_order @order = current_order diff --git a/app/views/split_checkout/_your_details.html.haml b/app/views/split_checkout/_your_details.html.haml index ff395fa334..8e1d47abe1 100644 --- a/app/views/split_checkout/_your_details.html.haml +++ b/app/views/split_checkout/_your_details.html.haml @@ -26,7 +26,7 @@ = bill_address.text_field :phone, { placeholder: t("split_checkout.step1.your_details.phone.placeholder") } = f.error_message_on "bill_address.phone" -%div.checkout-substep +%div.checkout-substep{ "data-controller": "dependant-select", "data-dependant-select-options-value": @countries_with_states } -# BILLING ADDRESS %div.checkout-title = t("split_checkout.step1.billing_address.title") @@ -52,8 +52,7 @@ %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :state_id, t("split_checkout.step1.billing_address.state_id.label") - -# todo: get state // values: s.id as s.name for s in countriesById[order.bill_address.country_id].states // defaultvalue: {} - = bill_address.select :state_id, [] + = bill_address.select :state_id, @countries_with_states, { }, { "data-dependant-select-target": "select" } %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| @@ -64,51 +63,48 @@ %div.checkout-input = fields_for :bill_address, @order.bill_address do |bill_address| = bill_address.label :country_id, t("split_checkout.step1.billing_address.country_id.label") - -# todo: get countries // values: c.id as c.name for c in countries // defaultvalue: order.bill_address.country_id = order.bill_address.country_id || #{DefaultCountry.id} - = bill_address.select :country_id, [] + = bill_address.select :country_id, @countries, { selected: @order.bill_address.country_id || DefaultCountry.id }, {"data-dependant-select-target": "source", "data-action": "dependant-select#handleSelectChange"} - 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) - %div.checkout-substep + %div.checkout-substep{ "data-controller": "toggle shippingmethod" } -# DELIVERY ADDRESS %div.checkout-title = t("split_checkout.step1.delivery_address.title") - = @shipping_methods.each do |shipping_method| + - @shipping_methods.each do |shipping_method| %div.checkout-input = fields_for shipping_method do |shipping_method_form| - = shipping_method_form.check_box :name, {id: "shipping_method_" + shipping_method.id.to_s }, shipping_method.id, 0 - = shipping_method_form.label :name, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s } + = shipping_method_form.radio_button :name, shipping_method.id, + id: "shipping_method_#{shipping_method.id}", + "data-description": shipping_method.description, + "data-action": "toggle#toggle shippingmethod#selectShippingMethod", + "data-toggle-show": shipping_method.require_ship_address + = shipping_method_form.label shipping_method.id, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s } %em.light - -# todo: display method price | localizeCurrency // How to retrive shipping method price? - = if shipping_method.price && shipping_method.price != 0 - = method.price - - else - = t(:checkout_method_free) + = payment_method_price(shipping_method, @order) - - if true # todo: Checkout.requireShipAddress() // requireShipAddress: -> @shippingMethod()?.require_ship_address - %div.checkout-input - = f.check_box "Checkout.ship_address_same_as_billing", { id: "Checkout.ship_address_same_as_billing" } # "ng-model": "Checkout.ship_address_same_as_billing"}s - = f.label "Checkout.ship_address_same_as_billing", t(:checkout_address_same), { for: "Checkout.ship_address_same_as_billing" } - - - if spree_current_user || true # todo: && Checkout.requireShipAddress() // requireShipAddress: -> @shippingMethod()?.require_ship_address - %div.checkout-input + %div.checkout-input{ "data-toggle-target": "content", style: "display: none" } + = f.check_box "Checkout.ship_address_same_as_billing", { id: "Checkout.ship_address_same_as_billing" } + = f.label "Checkout.ship_address_same_as_billing", t(:checkout_address_same), { for: "Checkout.ship_address_same_as_billing" } + + - if spree_current_user + %div.checkout-input{ "data-toggle-target": "content", style: "display: none" } = f.check_box "Checkout.default_ship_address", { id: "Checkout.default_ship_address" } = f.label "Checkout.default_ship_address", t(:checkout_default_ship_address), { for: "Checkout.default_ship_address" } - - if true # todo: Checkout.shippingMethod().description - .div.checkout-input - #distributor_address.panel - %span{}{{ Checkout.shippingMethod().description }} - %br/ - %br/ - - if @order.order_cycle.pickup_time_for(@order.distributor) - = t :checkout_ready_for - = @order.order_cycle.pickup_time_for(@order.distributor) + %div.checkout-input{"data-shippingmethod-target": "shippingMethodDescription"} + #distributor_address.panel + %span{"data-shippingmethod-target": "shippingMethodDescriptionContent"} + %br/ + %br/ + - if @order.order_cycle.pickup_time_for(@order.distributor) + = t :checkout_ready_for + = @order.order_cycle.pickup_time_for(@order.distributor) .div.checkout-input = f.label :special_instructions, t(:checkout_instructions) diff --git a/app/webpacker/controllers/dependant_select_controller.js b/app/webpacker/controllers/dependant_select_controller.js new file mode 100644 index 0000000000..b58e7c7417 --- /dev/null +++ b/app/webpacker/controllers/dependant_select_controller.js @@ -0,0 +1,27 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["source", "select"]; + static values = { options: Array }; + + connect() { + this.populateSelect(parseInt(this.sourceTarget.value)); + } + + handleSelectChange() { + this.populateSelect(parseInt(this.sourceTarget.value)); + } + + populateSelect(sourceId) { + const allOptions = this.optionsValue; + const options = allOptions.find((option) => option[0] === sourceId)[1]; + const selectBox = this.selectTarget; + selectBox.innerHTML = ""; + options.forEach((item) => { + const opt = document.createElement("option"); + opt.value = item[1]; + opt.innerHTML = item[0]; + selectBox.appendChild(opt); + }); + } +} diff --git a/app/webpacker/controllers/shippingmethod_controller.js b/app/webpacker/controllers/shippingmethod_controller.js new file mode 100644 index 0000000000..4a008581b3 --- /dev/null +++ b/app/webpacker/controllers/shippingmethod_controller.js @@ -0,0 +1,24 @@ +import { Controller } from "stimulus"; +export default class extends Controller { + static targets = [ + "shippingMethodDescription", + "shippingMethodDescriptionContent", + ]; + connect() { + // Hide shippingMethodDescription by default + this.shippingMethodDescriptionTarget.style.display = "none"; + } + selectShippingMethod(event) { + const input = event.target; + if (input.tagName === "INPUT") { + if (input.dataset.description.length > 0) { + this.shippingMethodDescriptionTarget.style.display = "block"; + this.shippingMethodDescriptionContentTarget.innerText = + input.dataset.description; + } else { + this.shippingMethodDescriptionTarget.style.display = "none"; + this.shippingMethodDescriptionContentTarget.innerText = null; + } + } + } +} diff --git a/app/webpacker/controllers/toggle_controller.js b/app/webpacker/controllers/toggle_controller.js new file mode 100644 index 0000000000..de87ad095d --- /dev/null +++ b/app/webpacker/controllers/toggle_controller.js @@ -0,0 +1,12 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["content"]; + + toggle(event) { + const input = event.currentTarget; + this.contentTargets.forEach((t) => { + t.style.display = input.dataset.toggleShow === "true" ? "block" : "none"; + }); + } +} From 91c4beea0f862abffc071ae778151a089d56581f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 22 Jul 2021 15:05:03 +0200 Subject: [PATCH 09/22] No need to add flash error for error as we render error on each input with error --- app/controllers/split_checkout_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 1134fcced7..48018ed179 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -246,7 +246,6 @@ class SplitCheckoutController < ::BaseController def checkout_failed(error = RuntimeError.new(order_error)) Bugsnag.notify(error) - flash[:error] = order_error if flash.blank? Checkout::PostCheckoutActions.new(@order).failure end From 939169c50405b46f393e5e70654a727c057fd7c5 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Sun, 25 Jul 2021 07:19:31 -0700 Subject: [PATCH 10/22] remove unused advance_to_state param --- app/controllers/split_checkout_controller.rb | 4 ++-- app/views/split_checkout/_form.html.haml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 48018ed179..a4a75b724f 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -173,8 +173,8 @@ class SplitCheckoutController < ::BaseController end end - def checkout_workflow(shipping_method_id, advance_to_state = "complete") - while @order.state != advance_to_state + def checkout_workflow(shipping_method_id) + while @order.state != "complete" if @order.state == "payment" return if redirect_to_payment_gateway diff --git a/app/views/split_checkout/_form.html.haml b/app/views/split_checkout/_form.html.haml index e71e87677c..7c8ebf418d 100644 --- a/app/views/split_checkout/_form.html.haml +++ b/app/views/split_checkout/_form.html.haml @@ -7,5 +7,4 @@ %div.checkout-step.medium-6 - if true ## test step - = hidden_field_tag "advance_to_state", "delivery" = render "split_checkout/your_details", f: f From 3d77ba49ac2f0436c685688cfc7e36ea06c9ade6 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Sun, 25 Jul 2021 07:57:03 -0700 Subject: [PATCH 11/22] pseudocode flow for steps --- app/controllers/split_checkout_controller.rb | 40 +++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index a4a75b724f..7cb2753f93 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -33,9 +33,10 @@ class SplitCheckoutController < ::BaseController helper 'spree/orders' def edit - redirect_to_step if request.path == "/checkout" return handle_redirect_from_stripe if valid_payment_intent_provided? + redirect_to_step if request.path == "/checkout" + # This is only required because of spree_paypal_express. If we implement # a version of paypal that uses this controller, and more specifically # the #action_failed method, then we can remove this call @@ -45,17 +46,28 @@ class SplitCheckoutController < ::BaseController end def update - params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) - return action_failed unless @order.update(params_adapter.params[:order] || {}) + if params.dig(:order).dig(:email) + # update the order + redirect_to checkout_payment_method_path + elsif params.dig(:payment_method) + # update the order + redirect_to checkout_order_summary_path + elsif params.dig(:order_confirmed) + # complete the order + redirect_to order_url(@order) + end - checkout_workflow(params_adapter.shipping_method_id) - rescue Spree::Core::GatewayError => e - rescue_from_spree_gateway_error(e) - rescue StandardError => e - flash[:error] = I18n.t("checkout.failed") - action_failed(e) - ensure - @order.update_order! + # params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) + # return action_failed unless @order.update(params_adapter.params[:order] || {}) + # + # checkout_workflow(params_adapter.shipping_method_id) + # rescue Spree::Core::GatewayError => e + # rescue_from_spree_gateway_error(e) + # rescue StandardError => e + # flash[:error] = I18n.t("checkout.failed") + # action_failed(e) + # ensure + # @order.update_order! end # Clears the cached order. Required for #current_order to return a new order @@ -69,13 +81,13 @@ class SplitCheckoutController < ::BaseController private def redirect_to_step - if @order.state = "payment" - if true#@order.payment_method_id.nil? + if @order.state == "payment" + if true# order.has_no_payment_method_chosen? redirect_to checkout_payment_method_path else redirect_to checkout_order_summary_path end - elsif @order.state == "cart" + else redirect_to checkout_your_details_path end end From 9b299974988f07a7494118b411343739452336ad Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 26 Jul 2021 10:55:37 +0200 Subject: [PATCH 12/22] Add selected class on form tab depending on the current path --- app/views/split_checkout/_tabs.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/split_checkout/_tabs.html.haml b/app/views/split_checkout/_tabs.html.haml index 58097a7617..5a2849b6b9 100644 --- a/app/views/split_checkout/_tabs.html.haml +++ b/app/views/split_checkout/_tabs.html.haml @@ -1,10 +1,10 @@ %div.flex - %div.columns.three.text-center.checkout-tab.selected + %div.columns.three.text-center.checkout-tab{"class": ("selected" if current_page?(checkout_your_details_path))} %span = t("split_checkout.your_details") - %div.columns.three.text-center.checkout-tab + %div.columns.three.text-center.checkout-tab{"class": ("selected" if current_page?(checkout_payment_method_path))} %span = t("split_checkout.payment_method") - %div.columns.three.text-center.checkout-tab + %div.columns.three.text-center.checkout-tab{"class": ("selected" if current_page?(checkout_summary_path))} %span = t("split_checkout.order_summary") From 57d719133262a4a374ddca948602dfb4ed39cdc0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 1 Jul 2021 15:03:18 +0200 Subject: [PATCH 13/22] Add new step, second one: payment method Add payment method form - select the right form depending on the path --- app/views/split_checkout/_form.html.haml | 6 +++-- .../split_checkout/_payment_method.html.haml | 22 +++++++++++++++++++ .../controllers/paymentmethod_controller.js | 9 ++++++++ config/locales/en.yml | 6 +++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 app/views/split_checkout/_payment_method.html.haml create mode 100644 app/webpacker/controllers/paymentmethod_controller.js diff --git a/app/views/split_checkout/_form.html.haml b/app/views/split_checkout/_form.html.haml index 7c8ebf418d..f60493a1c1 100644 --- a/app/views/split_checkout/_form.html.haml +++ b/app/views/split_checkout/_form.html.haml @@ -6,5 +6,7 @@ = form_with url: update_checkout_path, model: @order, method: :put do |f| %div.checkout-step.medium-6 - - if true ## test step - = render "split_checkout/your_details", f: f + - if current_page?(checkout_your_details_path) + = render 'split_checkout/your_details', f: f + - elsif current_page?(checkout_payment_method_path) + = render "split_checkout/payment_method", f: f diff --git a/app/views/split_checkout/_payment_method.html.haml b/app/views/split_checkout/_payment_method.html.haml new file mode 100644 index 0000000000..4a5308ba8e --- /dev/null +++ b/app/views/split_checkout/_payment_method.html.haml @@ -0,0 +1,22 @@ +%div.checkout-substep{"data": {"controller": "paymentmethod"}} + %div.checkout-title + = t("split_checkout.step2.payment_method.title") + - available_payment_methods.each do |payment_method| + %div.checkout-input + = f.radio_button :payment_method_id, payment_method.id, + id: "payment_method_#{payment_method.id}", + "data-action": "paymentmethod#selectPaymentMethod", + "data-paymentmethod-description": "#{payment_method.description}" + = f.label payment_method.id, "#{payment_method.name} (#{payment_method_price(payment_method, @order)})", {for: "payment_method_" + payment_method.id.to_s } + + %div + .panel{"data-paymentmethod-target": "panel", style: "display: none"} + + +%div.checkout-substep + = t("split_checkout.step2.explaination") + +%div.checkout-submit + = f.submit t("split_checkout.step2.submit"), class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false + %a.button.cancel{href: main_app.checkout_your_details_path} + = t("split_checkout.step2.cancel") diff --git a/app/webpacker/controllers/paymentmethod_controller.js b/app/webpacker/controllers/paymentmethod_controller.js new file mode 100644 index 0000000000..ac1c9dff60 --- /dev/null +++ b/app/webpacker/controllers/paymentmethod_controller.js @@ -0,0 +1,9 @@ +import { Controller } from "stimulus"; +export default class extends Controller { + static targets = ["panel"]; + + selectPaymentMethod(event) { + this.panelTarget.innerHTML = `${event.target.dataset.paymentmethodDescription}`; + this.panelTarget.style.display = "block"; + } +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 77abd79bcb..3112ac9da4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1661,6 +1661,12 @@ en: title: Delivery address submit: Next - Payment method cancel: Back to Edit basket + step2: + payment_method: + title: Payment method + explaination: You can review and confirm your order in the next step which includes the final costs. + submit: Next - Order summary + cancel: Back to Your details errors: required: Field cannot be blank invalid_number: "Please enter a valid phone number" From 6b430f94409f15116100032639147702cb66f6f0 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sat, 31 Jul 2021 18:49:31 +0100 Subject: [PATCH 14/22] Drop AMS injection for shipping methods We don't need this now --- app/views/split_checkout/_form.html.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/split_checkout/_form.html.haml b/app/views/split_checkout/_form.html.haml index f60493a1c1..97ecc74ffb 100644 --- a/app/views/split_checkout/_form.html.haml +++ b/app/views/split_checkout/_form.html.haml @@ -1,5 +1,4 @@ - content_for :injection_data do - = inject_available_shipping_methods = inject_available_payment_methods = inject_saved_credit_cards From cea2433c3ec290d5a83af355eaad6e6596c14e25 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sat, 31 Jul 2021 19:26:09 +0100 Subject: [PATCH 15/22] Fix indentation --- .../split_checkout/_your_details.html.haml | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/app/views/split_checkout/_your_details.html.haml b/app/views/split_checkout/_your_details.html.haml index 8e1d47abe1..a1170c7df2 100644 --- a/app/views/split_checkout/_your_details.html.haml +++ b/app/views/split_checkout/_your_details.html.haml @@ -70,47 +70,47 @@ = f.check_box :checkout_default_bill_address = f.label :checkout_default_bill_address, t(:checkout_default_bill_address) - %div.checkout-substep{ "data-controller": "toggle shippingmethod" } - -# DELIVERY ADDRESS - %div.checkout-title - = t("split_checkout.step1.delivery_address.title") +%div.checkout-substep{ "data-controller": "toggle shippingmethod" } + -# DELIVERY ADDRESS + %div.checkout-title + = t("split_checkout.step1.delivery_address.title") - - @shipping_methods.each do |shipping_method| - %div.checkout-input - = fields_for shipping_method do |shipping_method_form| - = shipping_method_form.radio_button :name, shipping_method.id, - id: "shipping_method_#{shipping_method.id}", - "data-description": shipping_method.description, - "data-action": "toggle#toggle shippingmethod#selectShippingMethod", - "data-toggle-show": shipping_method.require_ship_address - = shipping_method_form.label shipping_method.id, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s } - %em.light - = payment_method_price(shipping_method, @order) + - @shipping_methods.each do |shipping_method| + %div.checkout-input + = fields_for shipping_method do |shipping_method_form| + = shipping_method_form.radio_button :name, shipping_method.id, + id: "shipping_method_#{shipping_method.id}", + "data-description": shipping_method.description, + "data-action": "toggle#toggle shippingmethod#selectShippingMethod", + "data-toggle-show": shipping_method.require_ship_address + = shipping_method_form.label shipping_method.id, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s } + %em.light + = payment_method_price(shipping_method, @order) + %div.checkout-input{ "data-toggle-target": "content", style: "display: none" } + = f.check_box "Checkout.ship_address_same_as_billing", { id: "Checkout.ship_address_same_as_billing" } + = f.label "Checkout.ship_address_same_as_billing", t(:checkout_address_same), { for: "Checkout.ship_address_same_as_billing" } + + - if spree_current_user %div.checkout-input{ "data-toggle-target": "content", style: "display: none" } - = f.check_box "Checkout.ship_address_same_as_billing", { id: "Checkout.ship_address_same_as_billing" } - = f.label "Checkout.ship_address_same_as_billing", t(:checkout_address_same), { for: "Checkout.ship_address_same_as_billing" } - - - if spree_current_user - %div.checkout-input{ "data-toggle-target": "content", style: "display: none" } - = f.check_box "Checkout.default_ship_address", { id: "Checkout.default_ship_address" } - = f.label "Checkout.default_ship_address", t(:checkout_default_ship_address), { for: "Checkout.default_ship_address" } + = f.check_box "Checkout.default_ship_address", { id: "Checkout.default_ship_address" } + = f.label "Checkout.default_ship_address", t(:checkout_default_ship_address), { for: "Checkout.default_ship_address" } - %div.checkout-input{"data-shippingmethod-target": "shippingMethodDescription"} - #distributor_address.panel - %span{"data-shippingmethod-target": "shippingMethodDescriptionContent"} - %br/ - %br/ - - if @order.order_cycle.pickup_time_for(@order.distributor) - = t :checkout_ready_for - = @order.order_cycle.pickup_time_for(@order.distributor) + %div.checkout-input{"data-shippingmethod-target": "shippingMethodDescription"} + #distributor_address.panel + %span{"data-shippingmethod-target": "shippingMethodDescriptionContent"} + %br/ + %br/ + - if @order.order_cycle.pickup_time_for(@order.distributor) + = t :checkout_ready_for + = @order.order_cycle.pickup_time_for(@order.distributor) - .div.checkout-input - = f.label :special_instructions, t(:checkout_instructions) - = f.text_area :special_instructions, size: "60x4" + .div.checkout-input + = f.label :special_instructions, t(:checkout_instructions) + = f.text_area :special_instructions, size: "60x4" - %div.checkout-submit - = f.submit t("split_checkout.step1.submit"), class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false - %a.button.cancel{href: main_app.cart_path} - = t("split_checkout.step1.cancel") +%div.checkout-submit + = f.submit t("split_checkout.step1.submit"), class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false + %a.button.cancel{href: main_app.cart_path} + = t("split_checkout.step1.cancel") From d10899d9ea4bcf9dafa0f77c7d6769997f704d6b Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sat, 31 Jul 2021 19:28:33 +0100 Subject: [PATCH 16/22] Simplify use of nested field helpers --- .../split_checkout/_your_details.html.haml | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/app/views/split_checkout/_your_details.html.haml b/app/views/split_checkout/_your_details.html.haml index a1170c7df2..7c0e4fa336 100644 --- a/app/views/split_checkout/_your_details.html.haml +++ b/app/views/split_checkout/_your_details.html.haml @@ -1,74 +1,66 @@ -%div.checkout-substep - -# YOUR DETAILS - %div.checkout-title - = t("split_checkout.step1.your_details.title") += f.fields :bill_address, model: @order.bill_address do |bill_address| + %div.checkout-substep + -# YOUR DETAILS + %div.checkout-title + = t("split_checkout.step1.your_details.title") - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = bill_address.label :firstname, t("split_checkout.step1.your_details.first_name.label") = bill_address.text_field :firstname, { placeholder: t("split_checkout.step1.your_details.first_name.placeholder") } - = f.error_message_on "bill_address.firstname" - - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + = f.error_message_on "bill_address.firstname" + + %div.checkout-input = bill_address.label :lastname, t("split_checkout.step1.your_details.last_name.label") = bill_address.text_field :lastname, { placeholder: t("split_checkout.step1.your_details.last_name.placeholder") } - = f.error_message_on "bill_address.lastname" + = f.error_message_on "bill_address.lastname" - %div.checkout-input - = f.label :email, t("split_checkout.step1.your_details.email.label") - = f.text_field :email, { placeholder: t("split_checkout.step1.your_details.email.placeholder") } - = f.error_message_on :email + %div.checkout-input + = f.label :email, t("split_checkout.step1.your_details.email.label") + = f.text_field :email, { placeholder: t("split_checkout.step1.your_details.email.placeholder") } + = f.error_message_on :email - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = bill_address.label :phone, t("split_checkout.step1.your_details.phone.label") = bill_address.text_field :phone, { placeholder: t("split_checkout.step1.your_details.phone.placeholder") } - = f.error_message_on "bill_address.phone" + = f.error_message_on "bill_address.phone" -%div.checkout-substep{ "data-controller": "dependant-select", "data-dependant-select-options-value": @countries_with_states } - -# BILLING ADDRESS - %div.checkout-title - = t("split_checkout.step1.billing_address.title") + %div.checkout-substep{ "data-controller": "dependant-select", "data-dependant-select-options-value": @countries_with_states } + -# BILLING ADDRESS + %div.checkout-title + = t("split_checkout.step1.billing_address.title") - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = bill_address.label :address1, t("split_checkout.step1.billing_address.address1.label") = bill_address.text_field :address1, { placeholder: t("split_checkout.step1.billing_address.address1.placeholder") } - = f.error_message_on "bill_address.address1" + = f.error_message_on "bill_address.address1" - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = f.label :address2, t("split_checkout.step1.billing_address.address2.label") = f.text_field :address2, { placeholder: t("split_checkout.step1.billing_address.address2.placeholder") } - = f.error_message_on "bill_address.address2" + = f.error_message_on "bill_address.address2" - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = bill_address.label :city, t("split_checkout.step1.billing_address.city.label") = bill_address.text_field :city, { placeholder: t("split_checkout.step1.billing_address.city.placeholder") } - = f.error_message_on "bill_address.city" + = f.error_message_on "bill_address.city" - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = bill_address.label :state_id, t("split_checkout.step1.billing_address.state_id.label") = bill_address.select :state_id, @countries_with_states, { }, { "data-dependant-select-target": "select" } - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = bill_address.label :zipcode, t("split_checkout.step1.billing_address.zipcode.label") = bill_address.text_field :zipcode, { placeholder: t("split_checkout.step1.billing_address.zipcode.placeholder") } - = f.error_message_on "bill_address.zipcode" + = f.error_message_on "bill_address.zipcode" - %div.checkout-input - = fields_for :bill_address, @order.bill_address do |bill_address| + %div.checkout-input = bill_address.label :country_id, t("split_checkout.step1.billing_address.country_id.label") = bill_address.select :country_id, @countries, { selected: @order.bill_address.country_id || DefaultCountry.id }, {"data-dependant-select-target": "source", "data-action": "dependant-select#handleSelectChange"} - - 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) + - 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) %div.checkout-substep{ "data-controller": "toggle shippingmethod" } -# DELIVERY ADDRESS From aba3cf99a3c8ba98e6f8c44cef7e257445a2afd6 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sun, 1 Aug 2021 09:41:33 +0100 Subject: [PATCH 17/22] Fix address2 field --- app/views/split_checkout/_your_details.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/split_checkout/_your_details.html.haml b/app/views/split_checkout/_your_details.html.haml index 7c0e4fa336..def4ae8ba1 100644 --- a/app/views/split_checkout/_your_details.html.haml +++ b/app/views/split_checkout/_your_details.html.haml @@ -35,8 +35,8 @@ = f.error_message_on "bill_address.address1" %div.checkout-input - = f.label :address2, t("split_checkout.step1.billing_address.address2.label") - = f.text_field :address2, { placeholder: t("split_checkout.step1.billing_address.address2.placeholder") } + = bill_address.label :address2, t("split_checkout.step1.billing_address.address2.label") + = bill_address.text_field :address2, { placeholder: t("split_checkout.step1.billing_address.address2.placeholder") } = f.error_message_on "bill_address.address2" %div.checkout-input From a32ca23ca5b986f6e05206d1e612837d5ecb3e2a Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sun, 1 Aug 2021 09:42:11 +0100 Subject: [PATCH 18/22] Simplify routes and partials --- app/controllers/split_checkout_controller.rb | 13 +++++++++---- .../{_your_details.html.haml => _details.html.haml} | 0 app/views/split_checkout/_form.html.haml | 10 +++------- ..._payment_method.html.haml => _payment.html.haml} | 2 +- app/views/split_checkout/_tabs.html.haml | 6 +++--- config/routes.rb | 11 ++++++----- 6 files changed, 22 insertions(+), 20 deletions(-) rename app/views/split_checkout/{_your_details.html.haml => _details.html.haml} (100%) rename app/views/split_checkout/{_payment_method.html.haml => _payment.html.haml} (93%) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 7cb2753f93..367552a0ba 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -15,6 +15,7 @@ class SplitCheckoutController < ::BaseController # Otherwise we fail on duplicate indexes or end up with negative stock. prepend_around_action CurrentOrderLocker, only: [:edit, :update] + prepend_before_action :set_checkout_step prepend_before_action :check_hub_ready_for_checkout prepend_before_action :check_order_cycle_expiry prepend_before_action :require_order_cycle @@ -35,7 +36,7 @@ class SplitCheckoutController < ::BaseController def edit return handle_redirect_from_stripe if valid_payment_intent_provided? - redirect_to_step if request.path == "/checkout" + redirect_to_step unless @checkout_step # This is only required because of spree_paypal_express. If we implement # a version of paypal that uses this controller, and more specifically @@ -80,15 +81,19 @@ class SplitCheckoutController < ::BaseController private + def set_checkout_step + @checkout_step = params[:step] + end + def redirect_to_step if @order.state == "payment" if true# order.has_no_payment_method_chosen? - redirect_to checkout_payment_method_path + redirect_to checkout_step_path(:payment) else - redirect_to checkout_order_summary_path + redirect_to checkout_step_path(:summary) end else - redirect_to checkout_your_details_path + redirect_to checkout_step_path(:details) end end diff --git a/app/views/split_checkout/_your_details.html.haml b/app/views/split_checkout/_details.html.haml similarity index 100% rename from app/views/split_checkout/_your_details.html.haml rename to app/views/split_checkout/_details.html.haml diff --git a/app/views/split_checkout/_form.html.haml b/app/views/split_checkout/_form.html.haml index 97ecc74ffb..84a1d12edd 100644 --- a/app/views/split_checkout/_form.html.haml +++ b/app/views/split_checkout/_form.html.haml @@ -2,10 +2,6 @@ = inject_available_payment_methods = inject_saved_credit_cards -= form_with url: update_checkout_path, model: @order, method: :put do |f| - - %div.checkout-step.medium-6 - - if current_page?(checkout_your_details_path) - = render 'split_checkout/your_details', f: f - - elsif current_page?(checkout_payment_method_path) - = render "split_checkout/payment_method", f: f +%div.checkout-step.medium-6 + = form_with url: checkout_update_path(@checkout_step), model: @order, method: :put do |f| + = render "split_checkout/#{@checkout_step}", f: f diff --git a/app/views/split_checkout/_payment_method.html.haml b/app/views/split_checkout/_payment.html.haml similarity index 93% rename from app/views/split_checkout/_payment_method.html.haml rename to app/views/split_checkout/_payment.html.haml index 4a5308ba8e..73e515a961 100644 --- a/app/views/split_checkout/_payment_method.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -18,5 +18,5 @@ %div.checkout-submit = f.submit t("split_checkout.step2.submit"), class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false - %a.button.cancel{href: main_app.checkout_your_details_path} + %a.button.cancel{href: main_app.checkout_step_path(:details)} = t("split_checkout.step2.cancel") diff --git a/app/views/split_checkout/_tabs.html.haml b/app/views/split_checkout/_tabs.html.haml index 5a2849b6b9..3b29750a94 100644 --- a/app/views/split_checkout/_tabs.html.haml +++ b/app/views/split_checkout/_tabs.html.haml @@ -1,10 +1,10 @@ %div.flex - %div.columns.three.text-center.checkout-tab{"class": ("selected" if current_page?(checkout_your_details_path))} + %div.columns.three.text-center.checkout-tab{"class": ("selected" if @checkout_step == "details")} %span = t("split_checkout.your_details") - %div.columns.three.text-center.checkout-tab{"class": ("selected" if current_page?(checkout_payment_method_path))} + %div.columns.three.text-center.checkout-tab{"class": ("selected" if @checkout_step == "payment")} %span = t("split_checkout.payment_method") - %div.columns.three.text-center.checkout-tab{"class": ("selected" if current_page?(checkout_summary_path))} + %div.columns.three.text-center.checkout-tab{"class": ("selected" if @checkout_step == "summary")} %span = t("split_checkout.order_summary") diff --git a/config/routes.rb b/config/routes.rb index c0f68a28a9..6e42921cfa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,11 +71,12 @@ Openfoodnetwork::Application.routes.draw do constraints(SplitCheckoutConstraint.new) do get '/checkout', to: 'split_checkout#edit', as: :checkout - put '/checkout', to: 'split_checkout#update', as: :update_checkout - get '/checkout/enter-your-details', to: 'split_checkout#edit', as: :checkout_your_details - get '/checkout/payment-method', to: 'split_checkout#edit', as: :checkout_payment_method - get '/checkout/order-summary', to: 'split_checkout#edit', as: :checkout_summary - get '/checkout/:state', to: 'split_checkout#edit', as: :checkout_state + + 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 + get '/checkout/paypal_payment/:order_id', to: 'split_checkout#paypal_payment', as: :paypal_payment end From 2207aae67901c18fc906a42e824e42848a2f8da5 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sun, 1 Aug 2021 09:51:14 +0100 Subject: [PATCH 19/22] Add strong params for checkout We'll need to add more as we go --- app/controllers/split_checkout_controller.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 367552a0ba..dc7c898d7f 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -85,6 +85,14 @@ class SplitCheckoutController < ::BaseController @checkout_step = params[:step] end + def order_params + params.require(:order).permit( + :email, :shipping_method_id, :special_instructions, + bill_address_attributes: PermittedAttributes::Address.attributes, + ship_address_attributes: PermittedAttributes::Address.attributes + ) + end + def redirect_to_step if @order.state == "payment" if true# order.has_no_payment_method_chosen? From 6f2035febe5a0628d92bdd8bb32d523dcd1facd1 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sun, 1 Aug 2021 10:03:39 +0100 Subject: [PATCH 20/22] Update (provisional) checkout actions boilerplate --- app/controllers/split_checkout_controller.rb | 33 +++++++------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index dc7c898d7f..62e4a4d722 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -47,28 +47,19 @@ class SplitCheckoutController < ::BaseController end def update - if params.dig(:order).dig(:email) - # update the order - redirect_to checkout_payment_method_path - elsif params.dig(:payment_method) - # update the order - redirect_to checkout_order_summary_path - elsif params.dig(:order_confirmed) - # complete the order - redirect_to order_url(@order) - end + if @order.update(order_params) + OrderWorkflow.new(@order).advance_to_payment - # params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) - # return action_failed unless @order.update(params_adapter.params[:order] || {}) - # - # checkout_workflow(params_adapter.shipping_method_id) - # rescue Spree::Core::GatewayError => e - # rescue_from_spree_gateway_error(e) - # rescue StandardError => e - # flash[:error] = I18n.t("checkout.failed") - # action_failed(e) - # ensure - # @order.update_order! + if @order.state == "payment" + redirect_to checkout_step_path(:payment) + else + render :edit + end + else + flash[:error] = "Saving failed, please update the highlighted fields" + + render :edit + end end # Clears the cached order. Required for #current_order to return a new order From 937ba3ca0a4b6f5da0b459a9eab2ed8d7e11e20d Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sun, 1 Aug 2021 10:16:55 +0100 Subject: [PATCH 21/22] Ensure previously selected shipping method is selected in form --- app/views/split_checkout/_details.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/split_checkout/_details.html.haml b/app/views/split_checkout/_details.html.haml index def4ae8ba1..eee3c61ea7 100644 --- a/app/views/split_checkout/_details.html.haml +++ b/app/views/split_checkout/_details.html.haml @@ -67,11 +67,13 @@ %div.checkout-title = t("split_checkout.step1.delivery_address.title") + - selected_shipping_method = @order.shipping_method&.id - @shipping_methods.each do |shipping_method| %div.checkout-input = fields_for shipping_method do |shipping_method_form| = shipping_method_form.radio_button :name, shipping_method.id, id: "shipping_method_#{shipping_method.id}", + checked: (shipping_method.id == selected_shipping_method), "data-description": shipping_method.description, "data-action": "toggle#toggle shippingmethod#selectShippingMethod", "data-toggle-show": shipping_method.require_ship_address From 623c0eada56669dc7aed1f2c9afdcd2d868b8562 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Sun, 1 Aug 2021 11:30:10 +0100 Subject: [PATCH 22/22] Reinstate removed naming on checkout routes and tidy up --- config/routes.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 6e42921cfa..69d1561185 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,21 +69,19 @@ Openfoodnetwork::Application.routes.draw do resources :webhooks, only: [:create] end - constraints(SplitCheckoutConstraint.new) do - get '/checkout', to: 'split_checkout#edit', as: :checkout + constraints SplitCheckoutConstraint.new do + get '/checkout', to: 'split_checkout#edit' 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 - - get '/checkout/paypal_payment/:order_id', to: 'split_checkout#paypal_payment', as: :paypal_payment end get '/checkout', to: 'checkout#edit' - put '/checkout', to: 'checkout#update' - get '/checkout/:state', to: 'checkout#edit' - get '/checkout/paypal_payment/:order_id', to: 'checkout#paypal_payment' + put '/checkout', to: 'checkout#update', as: :update_checkout + get '/checkout/:state', to: 'checkout#edit', as: :checkout_state + get '/checkout/paypal_payment/:order_id', to: 'checkout#paypal_payment', as: :paypal_payment get 'embedded_shopfront/shopfront_session', to: 'application#shopfront_session' post 'embedded_shopfront/enable', to: 'application#enable_embedded_styles'