diff --git a/app/controllers/admin/vouchers_controller.rb b/app/controllers/admin/vouchers_controller.rb index efd9ac9b7d..3f190a8001 100644 --- a/app/controllers/admin/vouchers_controller.rb +++ b/app/controllers/admin/vouchers_controller.rb @@ -9,14 +9,11 @@ module Admin end def create - voucher_params = permitted_resource_params.merge(enterprise: @enterprise) - @voucher = Voucher.create(voucher_params) + @voucher = Voucher.create(permitted_resource_params.merge(enterprise: @enterprise)) if @voucher.save - redirect_to( - "#{edit_admin_enterprise_path(@enterprise)}#vouchers_panel", - flash: { success: flash_message_for(@voucher, :successfully_created) } - ) + flash[:success] = flash_message_for(@voucher, :successfully_created) + redirect_to edit_admin_enterprise_path(@enterprise, anchor: :vouchers_panel) else flash[:error] = @voucher.errors.full_messages.to_sentence render :new diff --git a/app/controllers/concerns/checkout_callbacks.rb b/app/controllers/concerns/checkout_callbacks.rb index 159c6041ea..7da81a592b 100644 --- a/app/controllers/concerns/checkout_callbacks.rb +++ b/app/controllers/concerns/checkout_callbacks.rb @@ -14,7 +14,7 @@ module CheckoutCallbacks prepend_before_action :require_order_cycle prepend_before_action :require_distributor_chosen - before_action :load_order, :associate_user, :load_saved_addresses, :load_saved_credit_cards + before_action :load_order, :associate_user, :load_saved_addresses before_action :load_shipping_methods, if: -> { params[:step] == "details" } before_action :ensure_order_not_completed @@ -30,8 +30,6 @@ module CheckoutCallbacks @order.manual_shipping_selection = true @order.checkout_processing = true - @voucher_adjustment = @order.voucher_adjustments.first - redirect_to(main_app.shop_path) && return if redirect_to_shop? redirect_to_cart_path && return unless valid_order_line_items? end @@ -43,11 +41,6 @@ module CheckoutCallbacks @order.ship_address ||= finder.ship_address end - def load_saved_credit_cards - @saved_credit_cards = spree_current_user&.credit_cards&.with_payment_profile.to_a - @selected_card = nil - end - def load_shipping_methods @shipping_methods = available_shipping_methods.sort { |a, b| a.name.casecmp(b.name) } end diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 31f2368b69..6d714c258d 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -25,12 +25,12 @@ class SplitCheckoutController < ::BaseController redirect_to_step_based_on_order unless params[:step] check_step if params[:step] - flash_error_when_no_shipping_method_available if available_shipping_methods.none? + return if available_shipping_methods.any? + + flash[:error] = I18n.t('split_checkout.errors.no_shipping_methods_available') end def update - return process_voucher if params[:apply_voucher].present? - if confirm_order || update_order return if performed? @@ -60,27 +60,6 @@ class SplitCheckoutController < ::BaseController replace("#flashes", partial("shared/flashes", locals: { flashes: flash })) end - def render_voucher_section_or_redirect - respond_to do |format| - format.cable_ready { render_voucher_section } - format.html { redirect_to checkout_step_path(:payment) } - end - end - - # Using the power of cable_car we replace only the #voucher_section instead of reloading the page - def render_voucher_section - render( - status: :ok, - cable_ready: cable_car.replace( - "#voucher-section", - partial( - "split_checkout/voucher_section", - locals: { order: @order, voucher_adjustment: @order.voucher_adjustments.first } - ) - ) - ) - end - def order_error_messages # Remove ship_address.* errors if no shipping method is not selected remove_ship_address_errors if no_ship_address_needed? @@ -125,10 +104,6 @@ class SplitCheckoutController < ::BaseController end end - def flash_error_when_no_shipping_method_available - flash[:error] = I18n.t('split_checkout.errors.no_shipping_methods_available') - end - def check_payments_adjustments @order.payments.each(&:ensure_correct_adjustment) end @@ -201,40 +176,6 @@ class SplitCheckoutController < ::BaseController selected_shipping_method.first.require_ship_address == false end - def process_voucher - if add_voucher - VoucherAdjustmentsService.calculate(@order) - render_voucher_section_or_redirect - elsif @order.errors.present? - render_error - end - end - - def add_voucher - if params.dig(:order, :voucher_code).blank? - @order.errors.add(:voucher, I18n.t('split_checkout.errors.voucher_not_found')) - return false - end - - # Fetch Voucher - voucher = Voucher.find_by(code: params[:order][:voucher_code], enterprise: @order.distributor) - - if voucher.nil? - @order.errors.add(:voucher, I18n.t('split_checkout.errors.voucher_not_found')) - return false - end - - adjustment = voucher.create_adjustment(voucher.code, @order) - - unless adjustment.valid? - @order.errors.add(:voucher, I18n.t('split_checkout.errors.add_voucher_error')) - adjustment.errors.each { |error| @order.errors.import(error) } - return false - end - - true - end - def summary_step? params[:step] == "summary" end @@ -262,6 +203,7 @@ class SplitCheckoutController < ::BaseController def validate_payment! return true if params.dig(:order, :payments_attributes, 0, :payment_method_id).present? + return true if @order.zero_priced_order? @order.errors.add :payment_method, I18n.t('split_checkout.errors.select_a_payment_method') end diff --git a/app/controllers/voucher_adjustments_controller.rb b/app/controllers/voucher_adjustments_controller.rb index b45b60fd9b..c34570269c 100644 --- a/app/controllers/voucher_adjustments_controller.rb +++ b/app/controllers/voucher_adjustments_controller.rb @@ -1,32 +1,77 @@ # frozen_string_literal: true class VoucherAdjustmentsController < BaseController - include CablecarResponses + before_action :set_order + + def create + if add_voucher + VoucherAdjustmentsService.calculate(@order) + @order.update_totals_and_states + + update_payment_section + elsif @order.errors.present? + render_error + end + end def destroy - @order = current_order - @order.voucher_adjustments.find_by(id: params[:id])&.destroy - respond_to do |format| - format.cable_ready { render_voucher_section } - format.html { redirect_to checkout_step_path(:payment) } - end + update_payment_section end private - # Using the power of cable_car we replace only the #voucher_section instead of reloading the page - def render_voucher_section - render( - status: :ok, - cable_ready: cable_car.replace( + def set_order + @order = current_order + end + + def add_voucher + if voucher_params[:voucher_code].blank? + @order.errors.add(:voucher_code, I18n.t('split_checkout.errors.voucher_not_found')) + return false + end + + voucher = Voucher.find_by(code: voucher_params[:voucher_code], enterprise: @order.distributor) + + if voucher.nil? + @order.errors.add(:voucher_code, I18n.t('split_checkout.errors.voucher_not_found')) + return false + end + + adjustment = voucher.create_adjustment(voucher.code, @order) + + unless adjustment.valid? + @order.errors.add(:voucher_code, I18n.t('split_checkout.errors.add_voucher_error')) + adjustment.errors.each { |error| @order.errors.import(error) } + return false + end + + true + end + + def update_payment_section + render cable_ready: cable_car.replace( + selector: "#checkout-payment-methods", + html: render_to_string(partial: "split_checkout/payment", locals: { step: "payment" }) + ) + end + + def render_error + flash.now[:error] = @order.errors.full_messages.to_sentence + + render status: :unprocessable_entity, cable_ready: cable_car. + replace("#flashes", partial("shared/flashes", locals: { flashes: flash })). + replace( "#voucher-section", partial( "split_checkout/voucher_section", locals: { order: @order, voucher_adjustment: @order.voucher_adjustments.first } ) ) - ) + end + + def voucher_params + params.require(:order).permit(:voucher_code) end end diff --git a/app/helpers/checkout_helper.rb b/app/helpers/checkout_helper.rb index 371b92dfff..c6c2f8d0e7 100644 --- a/app/helpers/checkout_helper.rb +++ b/app/helpers/checkout_helper.rb @@ -120,6 +120,8 @@ module CheckoutHelper end def payment_or_shipping_price(method, order) + return unless method + price = method.compute_amount(order) if price.zero? t('checkout_method_free') diff --git a/app/helpers/spree/payment_methods_helper.rb b/app/helpers/spree/payment_methods_helper.rb index 50fe34d457..bfd4a72e26 100644 --- a/app/helpers/spree/payment_methods_helper.rb +++ b/app/helpers/spree/payment_methods_helper.rb @@ -5,11 +5,13 @@ module Spree def payment_method(payment) # hack to allow us to retrieve the name of a "deleted" payment method id = payment.payment_method_id + return if id.nil? + Spree::PaymentMethod.find_with_destroyed(id) end def payment_method_name(payment) - payment_method(payment).name + payment_method(payment)&.name end end end diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index 91c70e8c84..dead0129fc 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -23,7 +23,7 @@ module Spree go_to_state :delivery go_to_state :payment, if: ->(order) { order.update_totals - order.payment_required? + order.payment_required? || order.zero_priced_order? } go_to_state :confirmation go_to_state :complete @@ -219,6 +219,12 @@ module Spree total.to_f > 0.0 && !skip_payment_for_subscription? end + # There are items present in the order, but either the items have zero price, + # or the order's total has been modified (maybe discounted) to zero. + def zero_priced_order? + line_items.count.positive? && total.zero? + end + # Returns the relevant zone (if any) to be used for taxation purposes. # Uses default tax zone unless there is a specific match def tax_zone @@ -615,6 +621,10 @@ module Spree raise Core::GatewayError, Spree.t(:no_pending_payments) if pending_payments.empty? pending_payments.each do |payment| + if payment.amount.zero? && zero_priced_order? + payment.update_columns(state: "completed", captured_at: Time.zone.now) + end + break if payment_total >= total yield payment diff --git a/app/services/voucher_adjustments_service.rb b/app/services/voucher_adjustments_service.rb index 2569010d95..ccadb8eb75 100644 --- a/app/services/voucher_adjustments_service.rb +++ b/app/services/voucher_adjustments_service.rb @@ -19,8 +19,10 @@ class VoucherAdjustmentsService # For now we just assume it is either all tax included in price or all tax excluded from price. if order.additional_tax_total.positive? handle_tax_excluded_from_price(order, amount) - else + elsif order.included_tax_total.positive? handle_tax_included_in_price(order, amount) + else + adjustment.amount = amount end # Move to closed state diff --git a/app/views/split_checkout/_details.html.haml b/app/views/split_checkout/_details.html.haml index e81c8c08a7..53b4e29fdd 100644 --- a/app/views/split_checkout/_details.html.haml +++ b/app/views/split_checkout/_details.html.haml @@ -1,155 +1,156 @@ -.medium-6 - = f.fields :bill_address, model: @order.bill_address do |bill_address| - %div.checkout-substep - -# YOUR DETAILS - %div.checkout-title - = t("split_checkout.step1.contact_information.title") += form_with url: checkout_update_path(checkout_step), model: @order, method: :put, data: { remote: "true" } do |f| + .medium-6 + = f.fields :bill_address, model: @order.bill_address do |bill_address| + %div.checkout-substep + -# YOUR DETAILS + %div.checkout-title + = t("split_checkout.step1.contact_information.title") - .two-columns-inputs - %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } - = f.label :email, t("split_checkout.step1.contact_information.email.label") - = f.text_field :email, { placeholder: " " } - = f.error_message_on :email + .two-columns-inputs + %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } + = f.label :email, t("split_checkout.step1.contact_information.email.label") + = f.text_field :email, { placeholder: " " } + = f.error_message_on :email - %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } - = bill_address.label :phone, t("split_checkout.step1.contact_information.phone.label") - = bill_address.text_field :phone, { placeholder: " " } - = f.error_message_on "bill_address.phone" + %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } + = bill_address.label :phone, t("split_checkout.step1.contact_information.phone.label") + = bill_address.text_field :phone, { placeholder: " " } + = f.error_message_on "bill_address.phone" - %div.checkout-substep - -# BILLING ADDRESS - %div.checkout-title - = t("split_checkout.step1.billing_address.title") + %div.checkout-substep + -# BILLING ADDRESS + %div.checkout-title + = t("split_checkout.step1.billing_address.title") - .two-columns-inputs - %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } - = bill_address.label :firstname, t("split_checkout.step1.billing_address.first_name.label") - = bill_address.text_field :firstname, { placeholder: " " } - = f.error_message_on "bill_address.firstname" + .two-columns-inputs + %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } + = bill_address.label :firstname, t("split_checkout.step1.billing_address.first_name.label") + = bill_address.text_field :firstname, { placeholder: " " } + = f.error_message_on "bill_address.firstname" - %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } - = bill_address.label :lastname, t("split_checkout.step1.billing_address.last_name.label") - = bill_address.text_field :lastname, { placeholder: " " } - = f.error_message_on "bill_address.lastname" - - %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = bill_address.label :address1, t("split_checkout.step1.address.address1.label") - = bill_address.text_field :address1, { placeholder: " " } - = f.error_message_on "bill_address.address1" - - %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = bill_address.label :address2, t("split_checkout.step1.address.address2.label") - = bill_address.text_field :address2, { placeholder: " " } - = f.error_message_on "bill_address.address2" - - %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = bill_address.label :city, t("split_checkout.step1.address.city.label") - = bill_address.text_field :city, { placeholder: " " } - = f.error_message_on "bill_address.city" - - %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = bill_address.label :zipcode, t("split_checkout.step1.address.zipcode.label") - = bill_address.text_field :zipcode, { placeholder: " " } - = f.error_message_on "bill_address.zipcode" - - %div{ "data-controller": "dependent-select", "data-dependent-select-options-value": countries_with_states } - - bill_address_country = @order.bill_address.country || DefaultCountry.country - - %div.checkout-input - = bill_address.label :country_id, t("split_checkout.step1.address.country_id.label") - = bill_address.select :country_id, countries, { selected: bill_address_country.id }, { "data-dependent-select-target": "source", "data-action": "dependent-select#handleSelectChange" } - - %div.checkout-input - = bill_address.label :state_id, t("split_checkout.step1.address.state_id.label") - = bill_address.select :state_id, states_for_country(bill_address_country), { selected: @order.bill_address&.state_id }, { "data-dependent-select-target": "select" } - - - if spree_current_user - %div.checkout-input - = 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] - %div.checkout-title - = t("split_checkout.step1.shipping_info.title") - - - display_ship_address = false - - ship_method_description = nil - - - selected_shipping_method ||= @shipping_methods[0].id if @shipping_methods.length == 1 - - @shipping_methods.each do |shipping_method| - - ship_method_is_selected = shipping_method.id == selected_shipping_method.to_i - %div.checkout-input.checkout-input-radio - = fields_for shipping_method do |shipping_method_form| - = shipping_method_form.radio_button :name, shipping_method.id, - id: "shipping_method_#{shipping_method.id}", - checked: ship_method_is_selected, - name: "shipping_method_id", - "data-requireAddress": shipping_method.require_ship_address, - "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.fees= payment_or_shipping_price(shipping_method, @order) - - display_ship_address = display_ship_address || (ship_method_is_selected && shipping_method.require_ship_address) - %div.checkout-input{"data-shippingmethod-target": "shippingMethodDescription", "data-shippingmethodid": shipping_method.id , style: "display: #{ship_method_is_selected ? 'block' : 'none'}" } - #distributor_address.panel - - if shipping_method.description.present? - %span #{shipping_method.description} - %br/ - %br/ - - if @order.order_cycle.pickup_time_for(@order.distributor) - = t :checkout_ready_for - = @order.order_cycle.pickup_time_for(@order.distributor) - - = f.error_message_on :shipping_method, standalone: true - - %div.checkout-input{ "data-toggle-target": "content", style: "display: #{display_ship_address ? 'block' : 'none'}" } - = f.check_box :ship_address_same_as_billing, { id: "ship_address_same_as_billing", name: "ship_address_same_as_billing", "data-action": "shippingmethod#showHideShippingAddress", "data-shippingmethod-target": "shippingAddressCheckbox", checked: shipping_and_billing_match?(@order) }, 1, nil - = f.label :ship_address_same_as_billing, t(:checkout_address_same), { for: "ship_address_same_as_billing" } - - %div{"data-shippingmethod-target": "shippingMethodAddress", style: "display: #{!display_ship_address || shipping_and_billing_match?(@order) ? 'none' : 'block'}" } - = f.fields :ship_address, model: @order.ship_address do |ship_address| - %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = ship_address.label :address1, t("split_checkout.step1.address.address1.label") - = ship_address.text_field :address1, { placeholder: " " } - = f.error_message_on "ship_address.address1" + %div.checkout-input.with-floating-label{ "data-controller": "floating-label" } + = bill_address.label :lastname, t("split_checkout.step1.billing_address.last_name.label") + = bill_address.text_field :lastname, { placeholder: " " } + = f.error_message_on "bill_address.lastname" %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = ship_address.label :address2, t("split_checkout.step1.address.address2.label") - = ship_address.text_field :address2, { placeholder: " " } - = f.error_message_on "ship_address.address2" + = bill_address.label :address1, t("split_checkout.step1.address.address1.label") + = bill_address.text_field :address1, { placeholder: " " } + = f.error_message_on "bill_address.address1" %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = ship_address.label :city, t("split_checkout.step1.address.city.label") - = ship_address.text_field :city, { placeholder: " " } - = f.error_message_on "ship_address.city" + = bill_address.label :address2, t("split_checkout.step1.address.address2.label") + = bill_address.text_field :address2, { placeholder: " " } + = f.error_message_on "bill_address.address2" %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} - = ship_address.label :zipcode, t("split_checkout.step1.address.zipcode.label") - = ship_address.text_field :zipcode, { placeholder: " " } - = f.error_message_on "ship_address.zipcode" + = bill_address.label :city, t("split_checkout.step1.address.city.label") + = bill_address.text_field :city, { placeholder: " " } + = f.error_message_on "bill_address.city" + + %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} + = bill_address.label :zipcode, t("split_checkout.step1.address.zipcode.label") + = bill_address.text_field :zipcode, { placeholder: " " } + = f.error_message_on "bill_address.zipcode" %div{ "data-controller": "dependent-select", "data-dependent-select-options-value": countries_with_states } - - ship_address_country = @order.ship_address.country || DefaultCountry.country + - bill_address_country = @order.bill_address.country || DefaultCountry.country %div.checkout-input - = ship_address.label :country_id, t("split_checkout.step1.address.country_id.label") - = ship_address.select :country_id, countries, { selected: ship_address_country.id }, { "data-dependent-select-target": "source", "data-action": "dependent-select#handleSelectChange" } + = bill_address.label :country_id, t("split_checkout.step1.address.country_id.label") + = bill_address.select :country_id, countries, { selected: bill_address_country.id }, { "data-dependent-select-target": "source", "data-action": "dependent-select#handleSelectChange" } %div.checkout-input - = ship_address.label :state_id, t("split_checkout.step1.address.state_id.label") - = ship_address.select :state_id, states_for_country(ship_address_country), { selected: @order.ship_address&.state_id }, { "data-dependent-select-target": "select" } + = bill_address.label :state_id, t("split_checkout.step1.address.state_id.label") + = bill_address.select :state_id, states_for_country(bill_address_country), { selected: @order.bill_address&.state_id }, { "data-dependent-select-target": "select" } + + - if spree_current_user + %div.checkout-input + = 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] + %div.checkout-title + = t("split_checkout.step1.shipping_info.title") + + - display_ship_address = false + - ship_method_description = nil + + - selected_shipping_method ||= @shipping_methods[0].id if @shipping_methods.length == 1 + - @shipping_methods.each do |shipping_method| + - ship_method_is_selected = shipping_method.id == selected_shipping_method.to_i + %div.checkout-input.checkout-input-radio + = fields_for shipping_method do |shipping_method_form| + = shipping_method_form.radio_button :name, shipping_method.id, + id: "shipping_method_#{shipping_method.id}", + checked: ship_method_is_selected, + name: "shipping_method_id", + "data-requireAddress": shipping_method.require_ship_address, + "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.fees= payment_or_shipping_price(shipping_method, @order) + - display_ship_address = display_ship_address || (ship_method_is_selected && shipping_method.require_ship_address) + %div.checkout-input{"data-shippingmethod-target": "shippingMethodDescription", "data-shippingmethodid": shipping_method.id , style: "display: #{ship_method_is_selected ? 'block' : 'none'}" } + #distributor_address.panel + - if shipping_method.description.present? + %span #{shipping_method.description} + %br/ + %br/ + - if @order.order_cycle.pickup_time_for(@order.distributor) + = t :checkout_ready_for + = @order.order_cycle.pickup_time_for(@order.distributor) + + = f.error_message_on :shipping_method, standalone: true - - if spree_current_user %div.checkout-input{ "data-toggle-target": "content", style: "display: #{display_ship_address ? 'block' : 'none'}" } - = f.check_box :save_ship_address - = f.label :save_ship_address, t(:checkout_default_ship_address) + = f.check_box :ship_address_same_as_billing, { id: "ship_address_same_as_billing", name: "ship_address_same_as_billing", "data-action": "shippingmethod#showHideShippingAddress", "data-shippingmethod-target": "shippingAddressCheckbox", checked: shipping_and_billing_match?(@order) }, 1, nil + = f.label :ship_address_same_as_billing, t(:checkout_address_same), { for: "ship_address_same_as_billing" } - .div.checkout-input - = f.label :special_instructions, t(:checkout_instructions) - = f.text_area :special_instructions, size: "60x4" + %div{"data-shippingmethod-target": "shippingMethodAddress", style: "display: #{!display_ship_address || shipping_and_billing_match?(@order) ? 'none' : 'block'}" } + = f.fields :ship_address, model: @order.ship_address do |ship_address| + %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} + = ship_address.label :address1, t("split_checkout.step1.address.address1.label") + = ship_address.text_field :address1, { placeholder: " " } + = f.error_message_on "ship_address.address1" - %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-input.with-floating-label{ "data-controller": "floating-label"} + = ship_address.label :address2, t("split_checkout.step1.address.address2.label") + = ship_address.text_field :address2, { placeholder: " " } + = f.error_message_on "ship_address.address2" + + %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} + = ship_address.label :city, t("split_checkout.step1.address.city.label") + = ship_address.text_field :city, { placeholder: " " } + = f.error_message_on "ship_address.city" + + %div.checkout-input.with-floating-label{ "data-controller": "floating-label"} + = ship_address.label :zipcode, t("split_checkout.step1.address.zipcode.label") + = ship_address.text_field :zipcode, { placeholder: " " } + = f.error_message_on "ship_address.zipcode" + + %div{ "data-controller": "dependent-select", "data-dependent-select-options-value": countries_with_states } + - ship_address_country = @order.ship_address.country || DefaultCountry.country + + %div.checkout-input + = ship_address.label :country_id, t("split_checkout.step1.address.country_id.label") + = ship_address.select :country_id, countries, { selected: ship_address_country.id }, { "data-dependent-select-target": "source", "data-action": "dependent-select#handleSelectChange" } + + %div.checkout-input + = ship_address.label :state_id, t("split_checkout.step1.address.state_id.label") + = ship_address.select :state_id, states_for_country(ship_address_country), { selected: @order.ship_address&.state_id }, { "data-dependent-select-target": "select" } + + - if spree_current_user + %div.checkout-input{ "data-toggle-target": "content", style: "display: #{display_ship_address ? 'block' : 'none'}" } + = 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) + = 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/_form.html.haml b/app/views/split_checkout/_form.html.haml index 0d32cac7c5..afedf10ebd 100644 --- a/app/views/split_checkout/_form.html.haml +++ b/app/views/split_checkout/_form.html.haml @@ -1,8 +1,5 @@ - content_for :injection_data do = inject_saved_credit_cards -%div.checkout-step{"class": if checkout_step?(:summary) then "checkout-summary" end} - = form_with url: checkout_update_path(checkout_step), model: @order, method: :put, - data: { remote: "true" } do |form| - - = render "split_checkout/#{checkout_step}", f: form +%div.checkout-step{ class: "#{'checkout-summary' if checkout_step?(:summary)}" } + = render "split_checkout/#{checkout_step}" diff --git a/app/views/split_checkout/_payment.html.haml b/app/views/split_checkout/_payment.html.haml index 82d3adcb3a..675fbfd820 100644 --- a/app/views/split_checkout/_payment.html.haml +++ b/app/views/split_checkout/_payment.html.haml @@ -1,37 +1,44 @@ -.medium-6 - %div.checkout-substep{"data-controller": "paymentmethod"} - = render partial: "split_checkout/voucher_section", formats: [:cable_ready], locals: { order: @order, voucher_adjustment: @voucher_adjustment } - - %div.checkout-title - = t("split_checkout.step2.payment_method.title") +.medium-6#checkout-payment-methods + - if feature?(:vouchers, spree_current_user) && @order.distributor.vouchers.present? + %div.checkout-substep + = render partial: "split_checkout/voucher_section", locals: { order: @order, voucher_adjustment: @order.voucher_adjustments.first } - - selected_payment_method = @order.payments&.with_state(:checkout)&.first&.payment_method_id - - selected_payment_method ||= available_payment_methods[0].id if available_payment_methods.length == 1 - - available_payment_methods.each do |payment_method| - %div.checkout-input.checkout-input-radio - = f.radio_button :payment_method_id, payment_method.id, - id: "payment_method_#{payment_method.id}", - name: "order[payments_attributes][][payment_method_id]", - checked: (payment_method.id == selected_payment_method), - "data-action": "paymentmethod#selectPaymentMethod", - "data-paymentmethod-id": "#{payment_method.id}", - "data-paymentmethod-target": "input" - = f.label :payment_method_id, "#{payment_method.name}", for: "payment_method_#{payment_method.id}" - %em.fees=payment_or_shipping_price(payment_method, @order) + = form_with url: checkout_update_path(local_assigns[:step] || checkout_step), model: @order, method: :put, data: { remote: "true" } do |f| + %div.checkout-substep{"data-controller": "paymentmethod"} + %div.checkout-title + = t("split_checkout.step2.payment_method.title") - .paymentmethod-container{"data-paymentmethod-id": "#{payment_method.id}", style: "display: #{payment_method.id == selected_payment_method ? "block" : "none"}"} - - if payment_method.description && !payment_method.description.empty? - .paymentmethod-description.panel - #{payment_method.description} - .paymentmethod-form - = render partial: "split_checkout/payment/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } + - if @order.zero_priced_order? + %h3= t(:no_payment_required) + = hidden_field_tag "order[payments_attributes][][amount]", 0 + - else + - selected_payment_method = @order.payments&.with_state(:checkout)&.first&.payment_method_id + - selected_payment_method ||= available_payment_methods[0].id if available_payment_methods.length == 1 + - available_payment_methods.each do |payment_method| + %div.checkout-input.checkout-input-radio + = f.radio_button :payment_method_id, payment_method.id, + id: "payment_method_#{payment_method.id}", + name: "order[payments_attributes][][payment_method_id]", + checked: (payment_method.id == selected_payment_method), + "data-action": "paymentmethod#selectPaymentMethod", + "data-paymentmethod-id": "#{payment_method.id}", + "data-paymentmethod-target": "input" + = f.label :payment_method_id, "#{payment_method.name}", for: "payment_method_#{payment_method.id}" + %em.fees=payment_or_shipping_price(payment_method, @order) - = f.error_message_on :payment_method, standalone: true - - %div.checkout-substep - = t("split_checkout.step2.explaination") + .paymentmethod-container{"data-paymentmethod-id": "#{payment_method.id}", style: "display: #{payment_method.id == selected_payment_method ? "block" : "none"}"} + - if payment_method.description && !payment_method.description.empty? + .paymentmethod-description.panel + #{payment_method.description} + .paymentmethod-form + = render partial: "split_checkout/payment/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f } - %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_step_path(:details)} - = t("split_checkout.step2.cancel") + = f.error_message_on :payment_method, standalone: true + + %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_step_path(:details)} + = t("split_checkout.step2.cancel") diff --git a/app/views/split_checkout/_summary.html.haml b/app/views/split_checkout/_summary.html.haml index e395bd7411..e1e230043d 100644 --- a/app/views/split_checkout/_summary.html.haml +++ b/app/views/split_checkout/_summary.html.haml @@ -1,103 +1,111 @@ -.summary-main - = render partial: "split_checkout/already_ordered" if show_bought_items? && checkout_step?(:summary) - .checkout-substep - .checkout-title - = t("split_checkout.step3.delivery_details.title") - %a.summary-edit{href: main_app.checkout_step_path(:details)} - = t("split_checkout.step3.delivery_details.edit") += form_with url: checkout_update_path(checkout_step), model: @order, method: :put, data: { remote: "true" } do |f| + .summary-main + = render partial: "split_checkout/already_ordered" if show_bought_items? && checkout_step?(:summary) + .checkout-substep + .checkout-title + = t("split_checkout.step3.delivery_details.title") + %a.summary-edit{href: main_app.checkout_step_path(:details)} + = t("split_checkout.step3.delivery_details.edit") - .summary-subtitle - = @order.shipping_method.name - %em.fees= payment_or_shipping_price(@order.shipping_method, @order) - .two-columns - %div - .summary-subtitle - = t("split_checkout.step3.delivery_details.address") - %span - = @order.bill_address.firstname - = @order.bill_address.lastname - %div - = @order.bill_address.phone + .summary-subtitle + = @order.shipping_method.name + %em.fees= payment_or_shipping_price(@order.shipping_method, @order) + .two-columns %div - = @order.user.email if @order.user - %br - %div - = @order.bill_address.address1 - - unless @order.bill_address.address2.blank? + .summary-subtitle + = t("split_checkout.step3.delivery_details.address") + %span + = @order.bill_address.firstname + = @order.bill_address.lastname %div - = @order.bill_address.address2 - %div - = @order.bill_address.city - %div - = @order.bill_address.state - %div - = @order.bill_address.zipcode - %div - = @order.bill_address.country - - if @order.special_instructions.present? + = @order.bill_address.phone + %div + = @order.user.email if @order.user %br - %em - = @order.special_instructions - - if @order.shipping_method.description.present? - %div - .summary-subtitle - = t("split_checkout.step3.delivery_details.instructions") %div - = @order.shipping_method.description - - %hr + = @order.bill_address.address1 + - unless @order.bill_address.address2.blank? + %div + = @order.bill_address.address2 + %div + = @order.bill_address.city + %div + = @order.bill_address.state + %div + = @order.bill_address.zipcode + %div + = @order.bill_address.country + - if @order.special_instructions.present? + %br + %em + = @order.special_instructions + - if @order.shipping_method.description.present? + %div + .summary-subtitle + = t("split_checkout.step3.delivery_details.instructions") + %div + = @order.shipping_method.description - .checkout-substep - .checkout-title - = t("split_checkout.step3.payment_method.title") - %a.summary-edit{href: main_app.checkout_step_path(:payment)} - = t("split_checkout.step3.payment_method.edit") - .two-columns - %div + %hr + + .checkout-substep + .checkout-title + = t("split_checkout.step3.payment_method.title") + %a.summary-edit{href: main_app.checkout_step_path(:payment)} + = t("split_checkout.step3.payment_method.edit") + .two-columns - payment_method = last_payment_method(@order) - = payment_method&.name - %em.fees=payment_or_shipping_price(payment_method, @order) - - if payment_method&.description.present? %div - .summary-subtitle - = t("split_checkout.step3.payment_method.instructions") + - if payment_method + = payment_method.name + %em.fees + = payment_or_shipping_price(payment_method, @order) + - elsif @order.zero_priced_order? + %h4= t(:no_payment_required) + + - if payment_method&.description.present? %div - = last_payment_method(@order)&.description + .summary-subtitle + = t("split_checkout.step3.payment_method.instructions") + %div + = payment_method&.description - %div.checkout-substep - %div.checkout-title - = t("split_checkout.step3.order.title") - %a.summary-edit{href: main_app.cart_path} - = t("split_checkout.step3.order.edit") - - = render 'spree/orders/summary', order: @order, display_footer: false + %div.checkout-substep + %div.checkout-title + = t("split_checkout.step3.order.title") + %a.summary-edit{href: main_app.cart_path} + = t("split_checkout.step3.order.edit") + + = render 'spree/orders/summary', order: @order, display_footer: false -.summary-right{ "data-controller": "sticky", "data-sticky-target": "container" } - .summary-right-line.total - .summary-right-line-label= t :order_total_price - .summary-right-line-value#order_total= @order.display_total.to_html - - .summary-right-line - .summary-right-line-label= t :order_produce - .summary-right-line-value= display_checkout_subtotal(@order) + .summary-right{ "data-controller": "sticky", "data-sticky-target": "container" } + .summary-right-line.total + .summary-right-line-label= t :order_total_price + .summary-right-line-value#order_total= @order.display_total.to_html - - checkout_adjustments_for(@order, exclude: [:line_item]).reverse_each do |adjustment| .summary-right-line - -if adjustment.originator_type == 'Voucher' - .summary-right-line-label.voucher= adjustment.label - .summary-right-line-value.voucher= adjustment.display_amount.to_html - -else - .summary-right-line-label= adjustment.label - .summary-right-line-value= adjustment.display_amount.to_html + .summary-right-line-label= t :order_produce + .summary-right-line-value= display_checkout_subtotal(@order) - - if @order.total_tax > 0 - .summary-right-line - .summary-right-line-label= t :order_includes_tax - .summary-right-line-value#tax-row= display_checkout_tax_total(@order) - - .checkout-submit - - if any_terms_required?(@order.distributor) - = render partial: "terms_and_conditions", locals: { f: f } - = f.submit t("split_checkout.step3.submit"), name: "confirm_order", class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false + - checkout_adjustments_for(@order, exclude: [:line_item]).reverse_each do |adjustment| + .summary-right-line + - if adjustment.originator_type == 'Voucher' + .summary-right-line-label.voucher + = "#{t(:voucher)}:" + = adjustment.label + .summary-right-line-value.voucher= adjustment.display_amount.to_html + - else + .summary-right-line-label= adjustment.label + .summary-right-line-value= adjustment.display_amount.to_html + + - if @order.total_tax > 0 + .summary-right-line + .summary-right-line-label= t :order_includes_tax + .summary-right-line-value#tax-row= display_checkout_tax_total(@order) + + .checkout-submit + - if any_terms_required?(@order.distributor) + = render partial: "terms_and_conditions", locals: { f: f } + = 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/_voucher_section.cable_ready.haml b/app/views/split_checkout/_voucher_section.cable_ready.haml deleted file mode 100644 index a005b5698b..0000000000 --- a/app/views/split_checkout/_voucher_section.cable_ready.haml +++ /dev/null @@ -1,19 +0,0 @@ -%div#voucher-section - - if order.distributor.vouchers.present? - .checkout-title - = t("split_checkout.step2.voucher.apply_voucher") - .checkout-input - .two-columns-inputs.voucher{"data-controller": "toggle-button-disabled"} - - if voucher_adjustment.present? - %span.button.voucher-added - %i.ofn-i_051-check-big - = t("split_checkout.step2.voucher.voucher", voucher_amount: voucher_adjustment.originator.display_value) - = link_to t("split_checkout.step2.voucher.remove_code"), voucher_adjustment_path(id: voucher_adjustment.id), method: "delete", data: { confirm: t("split_checkout.step2.voucher.confirm_delete") } - - # This might not be true, ie payment method including a fee which wouldn't be covered by voucher or tax implication raising total to be bigger than the voucher amount ? - - if voucher_adjustment.originator.amount > order.total - .checkout-input - %span.formError.standalone - = t("split_checkout.step2.voucher.warning_forfeit_remaining_amount") - - else - = text_field_tag "[order][voucher_code]", params.dig(:order, :voucher_code), data: { action: "input->toggle-button-disabled#inputIsChanged", }, placeholder: t("split_checkout.step2.voucher.placeholder") , class: "voucher" - = submit_tag t("split_checkout.step2.voucher.apply"), name: "apply_voucher", disabled: true, class: "button cancel voucher", "data-disable-with": false, data: { "toggle-button-disabled-target": "button" } diff --git a/app/views/split_checkout/_voucher_section.html.haml b/app/views/split_checkout/_voucher_section.html.haml new file mode 100644 index 0000000000..fcbebda20b --- /dev/null +++ b/app/views/split_checkout/_voucher_section.html.haml @@ -0,0 +1,25 @@ +%div#voucher-section + .checkout-title + = t("split_checkout.step2.voucher.apply_voucher") + .checkout-input{"data-controller": "toggle-button-disabled"} + = form_with url: voucher_adjustments_path, model: @order, method: :post, data: { remote: true } do |form| + - if voucher_adjustment.present? + .two-columns-inputs.voucher + %span.button.voucher-added + %i.ofn-i_051-check-big + = t("split_checkout.step2.voucher.voucher", voucher_amount: voucher_adjustment.originator.display_value) + = link_to t("split_checkout.step2.voucher.remove_code"), voucher_adjustment_path(id: voucher_adjustment.id), method: "delete", data: { confirm: t("split_checkout.step2.voucher.confirm_delete") } + + - # This might not be true, ie payment method including a fee which wouldn't be covered by voucher or tax implication raising total to be bigger than the voucher amount ? + - if voucher_adjustment.originator.amount > order.pre_discount_total + .checkout-input + %span.formError.standalone + = t("split_checkout.step2.voucher.warning_forfeit_remaining_amount") + - else + .two-columns-inputs + %div.checkout-input + = form.text_field :voucher_code, value: params.dig(:order, :voucher_code), data: { action: "input->toggle-button-disabled#inputIsChanged" }, placeholder: t("split_checkout.step2.voucher.placeholder"), class: "voucher" + = form.error_message_on :voucher_code + + %div.checkout-input + = form.submit t("split_checkout.step2.voucher.apply"), disabled: true, class: "button cancel voucher-button", "data-disable-with": false, data: { "toggle-button-disabled-target": "button" } diff --git a/app/views/split_checkout/payment/_stripe_sca.html.haml b/app/views/split_checkout/payment/_stripe_sca.html.haml index 144ca2559a..4503c2829e 100644 --- a/app/views/split_checkout/payment/_stripe_sca.html.haml +++ b/app/views/split_checkout/payment/_stripe_sca.html.haml @@ -1,15 +1,17 @@ +- saved_credit_cards = spree_current_user&.credit_cards&.with_payment_profile.to_a + %div{"data-controller": "stripe-cards", "data-paymentmethod-id": "#{payment_method.id}" } - - if @saved_credit_cards.any? + - if saved_credit_cards.any? .checkout-input %label = t('split_checkout.step2.form.stripe.use_saved_card') = select_tag :existing_card_id, - options_for_select(stripe_card_options(@saved_credit_cards) + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], @selected_card), + options_for_select(stripe_card_options(saved_credit_cards) + [[t('split_checkout.step2.form.stripe.create_new_card'), ""]], nil), { "data-action": "change->stripe-cards#onSelectCard", "data-stripe-cards-target": "select" } %div{"data-stripe-cards-target": "stripeelements"} .checkout-input - - if @saved_credit_cards.none? + - if saved_credit_cards.none? %label = t('split_checkout.step2.form.stripe.use_new_card') diff --git a/app/views/spree/admin/payments/_list.html.haml b/app/views/spree/admin/payments/_list.html.haml index 4e1a1ec066..cbae3b95c6 100644 --- a/app/views/spree/admin/payments/_list.html.haml +++ b/app/views/spree/admin/payments/_list.html.haml @@ -11,7 +11,9 @@ %tr{class: "#{cycle('odd', 'even')}"} %td= pretty_time(payment.created_at) %td.align-center= payment.display_amount.to_html - %td.align-center= link_to payment_method_name(payment), spree.admin_order_payment_path(@order, payment) + %td.align-center + - if payment.payment_method_id + = link_to payment_method_name(payment), spree.admin_order_payment_path(@order, payment) %td.align-center %span{class: "state #{payment.state}"}= t(payment.state, scope: "spree.payment_states", default: payment.state.capitalize) %td.actions diff --git a/app/views/spree/orders/_totals_footer.html.haml b/app/views/spree/orders/_totals_footer.html.haml index ab8130210e..556160804b 100644 --- a/app/views/spree/orders/_totals_footer.html.haml +++ b/app/views/spree/orders/_totals_footer.html.haml @@ -12,6 +12,8 @@ %tr.total %td.text-right{:colspan => "3"} %strong + - if adjustment.originator_type == "Voucher" + = "#{t(:voucher)}:" = adjustment.label %td.text-right.total %span= adjustment.display_amount.to_html diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index b559074bf5..b3e5650984 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -11,11 +11,15 @@ %strong = order.display_total.to_html .pad - .text-big - = t :order_payment - %strong= last_payment_method(order)&.name - %p.text-small.text-skinny.pre-line.word-wrap - %em= last_payment_method(order)&.description + - if (order_payment_method = last_payment_method(order)) + .text-big + = t :order_payment + %strong= order_payment_method&.name + %p.text-small.text-skinny.pre-line.word-wrap + %em= order_payment_method&.description + - else + .text-big + = t(:no_payment_required) .order-summary.text-small %strong diff --git a/app/webpacker/css/darkswarm/split-checkout.scss b/app/webpacker/css/darkswarm/split-checkout.scss index 3d9c05b929..1fc90b9815 100644 --- a/app/webpacker/css/darkswarm/split-checkout.scss +++ b/app/webpacker/css/darkswarm/split-checkout.scss @@ -412,22 +412,18 @@ justify-content: normal; align-items: center; - input { - width: 50%; - } - a { color: inherit; } + } - .button { - &.cancel { - width: 30%; - border-radius: 0.5em; - padding:0; - height: 2.5em; - background-color: $teal-400 - } + .voucher-button { + &.cancel { + width: 30%; + border-radius: 0.35em; + padding:0; + height: 2.5em; + background-color: $teal-400 } } diff --git a/config/locales/en.yml b/config/locales/en.yml index fbd2515eae..5eda42785d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -450,6 +450,7 @@ en: none: None notes: Notes error: Error + voucher: Voucher processing_payment: "Processing payment..." no_pending_payments: "No pending payments" invalid_payment_state: "Invalid payment state: %{state}" @@ -2124,6 +2125,7 @@ en: order_not_paid: NOT PAID order_total: Total order order_payment: "Paying via:" + no_payment_required: "No payment required" order_billing_address: Billing address order_delivery_on: Delivery on order_delivery_address: Delivery address diff --git a/config/routes.rb b/config/routes.rb index 1d933363ac..c92481cb64 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,7 +112,7 @@ Openfoodnetwork::Application.routes.draw do get '/:id/shop', to: 'enterprises#shop', as: 'enterprise_shop' get "/enterprises/:permalink", to: redirect("/") # Legacy enterprise URL - resources :voucher_adjustments, only: [:destroy] + resources :voucher_adjustments, only: [:create, :destroy] get 'sitemap.xml', to: 'sitemap#index', defaults: { format: 'xml' } diff --git a/lib/reporting/reports/payments/payment_totals.rb b/lib/reporting/reports/payments/payment_totals.rb index 06a6d1cdb4..4e1b561994 100644 --- a/lib/reporting/reports/payments/payment_totals.rb +++ b/lib/reporting/reports/payments/payment_totals.rb @@ -23,7 +23,7 @@ module Reporting def total_by_payment_method(orders, pay_method) orders.map(&:payments).flatten.select { |payment| - payment.completed? && payment.payment_method.name.to_s.include?(pay_method) + payment.completed? && payment.payment_method&.name.to_s.include?(pay_method) }.sum(&:amount) end end diff --git a/lib/reporting/reports/payments/payments_by_payment_type.rb b/lib/reporting/reports/payments/payments_by_payment_type.rb index 67d96236be..89003205df 100644 --- a/lib/reporting/reports/payments/payments_by_payment_type.rb +++ b/lib/reporting/reports/payments/payments_by_payment_type.rb @@ -17,7 +17,7 @@ module Reporting { payment_state: proc { |payments| payment_state(payments.first.order) }, distributor: proc { |payments| payments.first.order.distributor.name }, - payment_type: proc { |payments| payments.first.payment_method.name }, + payment_type: proc { |payments| payments.first.payment_method&.name }, total_price: proc { |payments| payments.sum(&:amount) } } end diff --git a/spec/controllers/split_checkout_controller_spec.rb b/spec/controllers/split_checkout_controller_spec.rb index 114a947c39..b493f4ab21 100644 --- a/spec/controllers/split_checkout_controller_spec.rb +++ b/spec/controllers/split_checkout_controller_spec.rb @@ -212,6 +212,26 @@ describe SplitCheckoutController, type: :controller do end end + context "with a zero-priced order" do + let(:params) do + { step: "payment", order: { payments_attributes: [{ amount: 0 }] } } + end + + before do + order.line_items.first.update(price: 0) + order.update_totals_and_states + end + + it "allows proceeding to confirmation" do + put :update, params: params + + expect(response).to redirect_to checkout_step_path(:summary) + expect(order.reload.state).to eq "confirmation" + expect(order.payments.count).to eq 1 + expect(order.payments.first.amount).to eq 0 + end + end + context "with a saved credit card" do let!(:saved_card) { create(:stored_credit_card, user: user) } let(:checkout_params) do @@ -233,73 +253,6 @@ describe SplitCheckoutController, type: :controller do expect(order.payments.first.source.id).to eq saved_card.id end end - - describe "Vouchers" do - let(:voucher) { create(:voucher, code: 'some_code', enterprise: distributor) } - - describe "adding a voucher" do - let(:checkout_params) do - { - apply_voucher: "true", - order: { - voucher_code: voucher.code - } - } - end - - it "adds a voucher to the order" do - # Set the headers to simulate a cable_ready request - request.headers["accept"] = "text/vnd.cable-ready.json" - - put :update, params: params - - expect(response.status).to eq(200) - expect(order.reload.voucher_adjustments.length).to eq(1) - end - - context "when voucher doesn't exist" do - let(:checkout_params) do - { - apply_voucher: "true", - order: { - voucher_code: "non_voucher" - } - } - end - - it "returns 422 and an error message" do - put :update, params: params - - expect(response.status).to eq 422 - expect(flash[:error]).to match "Voucher Not found" - end - end - - context "when adding fails" do - it "returns 422 and an error message" do - # Create a non valid adjustment - adjustment = build(:adjustment, label: nil) - allow(voucher).to receive(:create_adjustment).and_return(adjustment) - allow(Voucher).to receive(:find_by).and_return(voucher) - - put :update, params: params - - expect(response.status).to eq 422 - expect(flash[:error]).to match( - "There was an error while adding the voucher and Label can't be blank" - ) - end - end - - context "with an html request" do - it "redirects to the payment step" do - put :update, params: params - - expect(response).to redirect_to(checkout_step_path(:payment)) - end - end - end - end end context "summary step" do diff --git a/spec/models/spree/order/payment_spec.rb b/spec/models/spree/order/payment_spec.rb index 224f0be0dc..c7eaa673b3 100644 --- a/spec/models/spree/order/payment_spec.rb +++ b/spec/models/spree/order/payment_spec.rb @@ -50,5 +50,22 @@ module Spree order.process_payments! end + + context "with a zero-priced order" do + let!(:zero_order) { + create(:order, state: "payment", line_items: [create(:line_item, price: 0)]) + } + let!(:zero_payment) { create(:payment, order: zero_order, amount: 0, state: "checkout") } + let(:updater) { OrderManagement::Order::Updater.new(zero_order) } + + it "processes payments successfully" do + zero_order.process_payments! + updater.update_payment_state + + expect(zero_order.payment_state).to eq "paid" + expect(zero_payment.reload.state).to eq "completed" + expect(zero_payment.captured_at).to_not be_nil + end + end end end diff --git a/spec/requests/voucher_adjustments_spec.rb b/spec/requests/voucher_adjustments_spec.rb index 9154f7ea32..8c93fb3f69 100644 --- a/spec/requests/voucher_adjustments_spec.rb +++ b/spec/requests/voucher_adjustments_spec.rb @@ -4,55 +4,96 @@ require 'spec_helper' describe VoucherAdjustmentsController, type: :request do let(:user) { order.user } + let(:address) { create(:address) } let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } - let(:order) { create( :order_with_line_items, line_items_count: 1, distributor: distributor) } + let(:order_cycle) { create(:order_cycle, distributors: [distributor]) } + let(:exchange) { order_cycle.exchanges.outgoing.first } + let(:order) do + create( + :order_with_line_items, + line_items_count: 1, + distributor: distributor, + order_cycle: order_cycle, + bill_address: address, + ship_address: address + ) + end + let(:shipping_method) { distributor.shipping_methods.first } let(:voucher) { create(:voucher, code: 'some_code', enterprise: distributor) } - let!(:adjustment) { voucher.create_adjustment(voucher.code, order) } before do - # Make sure the order is created by the order user, the factory doesn't set ip properly - order.created_by = user - order.save! + order.update!(created_by: user) + + order.select_shipping_method shipping_method.id + OrderWorkflow.new(order).advance_to_payment sign_in user end - describe "DELETE voucher_adjustments/:id" do - let(:cable_ready_header) { { accept: "text/vnd.cable-ready.json" } } + describe "POST voucher_adjustments" do + let(:params) { { order: { voucher_code: voucher.code } } } - context "with a cable ready request" do - it "deletes the voucher adjustment" do - delete("/voucher_adjustments/#{adjustment.id}", headers: cable_ready_header) + it "adds a voucher to the user's current order" do + post "/voucher_adjustments", params: params - expect(order.voucher_adjustments.length).to eq(0) - end + expect(response).to be_successful + expect(order.reload.voucher_adjustments.length).to eq(1) + end - it "render a succesful response" do - delete("/voucher_adjustments/#{adjustment.id}", headers: cable_ready_header) + context "when voucher doesn't exist" do + let(:params) { { order: { voucher_code: "non_voucher" } } } - expect(response).to be_successful - end + it "returns 422 and an error message" do + post "/voucher_adjustments", params: params - context "when adjustment doesn't exits" do - it "does nothing" do - delete "/voucher_adjustments/-1", headers: cable_ready_header - - expect(order.voucher_adjustments.length).to eq(1) - end - - it "render a succesful response" do - delete "/voucher_adjustments/-1", headers: cable_ready_header - - expect(response).to be_successful - end + expect(response).to be_unprocessable + expect(flash[:error]).to match "Voucher code Not found" end end - context "with an html request" do - it "redirect to checkout payment step" do - delete "/voucher_adjustments/#{adjustment.id}" + context "when adding fails" do + it "returns 422 and an error message" do + # Create a non valid adjustment + bad_adjustment = build(:adjustment, label: nil) + allow(voucher).to receive(:create_adjustment).and_return(bad_adjustment) + allow(Voucher).to receive(:find_by).and_return(voucher) - expect(response).to redirect_to(checkout_step_path(:payment)) + post "/voucher_adjustments", params: params + + expect(response).to be_unprocessable + expect(flash[:error]).to match( + "There was an error while adding the voucher and Label can't be blank" + ) + end + end + end + + describe "DELETE voucher_adjustments/:id" do + let!(:adjustment) { voucher.create_adjustment(voucher.code, order) } + + it "deletes the voucher adjustment" do + delete "/voucher_adjustments/#{adjustment.id}" + + expect(order.voucher_adjustments.reload.length).to eq(0) + end + + it "render a success response" do + delete "/voucher_adjustments/#{adjustment.id}" + + expect(response).to be_successful + end + + context "when adjustment doesn't exits" do + it "does nothing" do + delete "/voucher_adjustments/-1" + + expect(order.voucher_adjustments.reload.length).to eq(1) + end + + it "render a success response" do + delete "/voucher_adjustments/-1" + + expect(response).to be_successful end end end diff --git a/spec/system/consumer/split_checkout_spec.rb b/spec/system/consumer/split_checkout_spec.rb index 2bcc9f1cd0..148f2a212c 100644 --- a/spec/system/consumer/split_checkout_spec.rb +++ b/spec/system/consumer/split_checkout_spec.rb @@ -707,6 +707,8 @@ describe "As a consumer, I want to checkout my order" do end describe "vouchers" do + before { Flipper.enable :vouchers } + context "with no voucher available" do before do visit checkout_step_path(:payment) @@ -769,7 +771,7 @@ describe "As a consumer, I want to checkout my order" do fill_in "Enter voucher code", with: "non_code" click_button("Apply") - expect(page).to have_content("Voucher Not found") + expect(page).to have_content("Voucher code Not found") end end end @@ -786,9 +788,10 @@ describe "As a consumer, I want to checkout my order" do click_on "Remove code" end - within '.voucher' do + within '#voucher-section' do expect(page).to have_button("Apply", disabled: true) end + expect(order.voucher_adjustments.length).to eq(0) end end diff --git a/spec/system/consumer/split_checkout_tax_incl_spec.rb b/spec/system/consumer/split_checkout_tax_incl_spec.rb index 66edeff008..6663c6a970 100644 --- a/spec/system/consumer/split_checkout_tax_incl_spec.rb +++ b/spec/system/consumer/split_checkout_tax_incl_spec.rb @@ -106,6 +106,8 @@ describe "As a consumer, I want to see adjustment breakdown" do end context "when using a voucher" do + before { Flipper.enable :vouchers } + let!(:voucher) do create(:voucher, code: 'some_code', enterprise: distributor, amount: 10) end diff --git a/spec/system/consumer/split_checkout_tax_not_incl_spec.rb b/spec/system/consumer/split_checkout_tax_not_incl_spec.rb index d4a7ddda41..046a6ac483 100644 --- a/spec/system/consumer/split_checkout_tax_not_incl_spec.rb +++ b/spec/system/consumer/split_checkout_tax_not_incl_spec.rb @@ -63,6 +63,7 @@ describe "As a consumer, I want to see adjustment breakdown" do before do # assures tax is charged in dependence of shipping address Spree::Config.set(tax_using_ship_address: true) + Flipper.enable :vouchers end describe "a not-included tax" do @@ -115,7 +116,7 @@ describe "As a consumer, I want to see adjustment breakdown" do expect(page).to have_selector('#tax-row', text: with_currency(1.30)) end - pending "when using a voucher" do + context "when using a voucher" do let!(:voucher) do create(:voucher, code: 'some_code', enterprise: distributor, amount: 10) end @@ -126,9 +127,11 @@ describe "As a consumer, I want to see adjustment breakdown" do choose "Delivery" click_button "Next - Payment method" + # add Voucher fill_in "Enter voucher code", with: voucher.code click_button("Apply") + expect(page).to have_selector ".voucher-added" click_on "Next - Order summary" click_on "Complete order"