diff --git a/core/app/models/spree/credit_card.rb b/core/app/models/spree/credit_card.rb index 8135db88900..3f6403f64b5 100644 --- a/core/app/models/spree/credit_card.rb +++ b/core/app/models/spree/credit_card.rb @@ -5,12 +5,15 @@ class CreditCard < Spree::Base before_create :set_missing_info - attr_accessor :number, :verification_value, :encrypted_data + attr_accessor :encrypted_data, + :number, + :imported, + :verification_value validates :month, :year, numericality: { only_integer: true }, if: :require_card_numbers?, on: :create - validates :number, presence: true, if: :require_card_numbers?, on: :create + validates :number, presence: true, if: :require_card_numbers?, on: :create, unless: :imported validates :name, presence: true, if: :require_card_numbers?, on: :create - validates :verification_value, presence: true, if: :require_card_numbers?, on: :create + validates :verification_value, presence: true, if: :require_card_numbers?, on: :create, unless: :imported validate :expiry_not_in_the_past diff --git a/core/app/models/spree/line_item.rb b/core/app/models/spree/line_item.rb index 25517ef59f3..7d286d6078c 100644 --- a/core/app/models/spree/line_item.rb +++ b/core/app/models/spree/line_item.rb @@ -17,7 +17,7 @@ class LineItem < Spree::Base validates :quantity, numericality: { only_integer: true, greater_than: -1, - message: Spree.t('validation.must_be_int') + message: I18n.t('spree.validation.must_be_int') } validates :price, numericality: true validates_with Stock::AvailabilityValidator @@ -33,6 +33,7 @@ class LineItem < Spree::Base delegate :name, :description, :sku, :should_track_inventory?, to: :variant attr_accessor :target_shipment + attr_accessor :deleted def copy_price if variant @@ -95,6 +96,19 @@ def variant Spree::Variant.unscoped { super } end + + def single_vat + I18n.t("products.vat", vat: self.tax_category.tax_rates.first.amount * 100) + end + + def single_vat_name + I18n.t("products.vat", vat: self.tax_category.tax_rates.first.name.split(" ").last) + end + + def display_tax_description + I18n.t "cart.lineitem.#{self.tax_category.description.downcase.gsub(" ", "-")}" + end + private def update_inventory if (changed? || target_shipment.present?) && self.order.has_checkout_step?("delivery") diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb index b7dc211fa32..f9db43a66a3 100644 --- a/core/app/models/spree/order.rb +++ b/core/app/models/spree/order.rb @@ -1,10 +1,11 @@ -require 'spree/core/validators/email' require 'spree/order/checkout' module Spree class Order < Spree::Base include Spree::Order::Checkout include Spree::Order::CurrencyUpdater + include ActionView::Helpers::TextHelper + checkout_flow do go_to_state :address @@ -55,6 +56,8 @@ def states end end + + accepts_nested_attributes_for :line_items accepts_nested_attributes_for :bill_address accepts_nested_attributes_for :ship_address @@ -65,18 +68,20 @@ def states before_validation :set_currency before_validation :generate_order_number, on: :create before_validation :clone_billing_address, if: :use_billing? - attr_accessor :use_billing + before_validation :mirror_phone before_create :create_token before_create :link_by_email before_update :homogenize_line_item_currencies, if: :currency_changed? - validates :email, presence: true, if: :require_email - validates :email, email: true, if: :require_email, allow_blank: true + validates :email, email: true, presence: true, if: :require_on_address_page validates :number, uniqueness: true validate :has_available_shipment + validates :terms_of_service, acceptance: {accept: true, allow_nil: false}, if: :require_on_address_page + + make_permalink field: :number delegate :update_totals, :persist_totals, :to => :updater @@ -115,6 +120,49 @@ def self.register_update_hook(hook) self.update_hooks.add(hook) end + # state_machine do + # # before_transition all => all, do: :update_cart_info + # end + + # clear cart, tax and shipping info and recalculate + # this way a user can change her address and the free shipping + # is applied correctly (and adjust the cart) + # + def update_cart_info + if !shipments_available? || state == 'payment' + puts "." * 100 + + # # create shipments, deletes adjusments + create_proposed_shipments + + # per default use the first shipping method + select_a_possible_shipping_rate + + set_shipments_cost + refresh_shipment_rates + end + + # update shippment costs + # and free shipping + apply_free_shipping_promotions if shipments_available? + + # remove or add cod payment adjustments + update_adjustments_on_payment_change + + update_totals + end + + def select_a_possible_shipping_rate + if shipments_available? + shipments.first.selected_shipping_rate_id = shipments.first.shipping_rates.first + end + end + + def shipments_available? + shipments.present? && !shipments.any? { |shipment| !shipment.valid? || shipment.shipping_rates.blank? } + end + + def all_adjustments Adjustment.where("order_id = :order_id OR (adjustable_id = :order_id AND adjustable_type = 'Spree::Order')", order_id: self.id) @@ -174,6 +222,31 @@ def completed? completed_at.present? end + def require_on_address_page + # true unless state == 'cart' || new_record? or ['cart'].include?(state) + return false if state == 'cart' + true + end + + def mirror_phone + if ship_address.present? && bill_address.present? + ship_address.phone = bill_address.phone + end + end + + def payed? + self.payments.where.not("state" => "invalid").any? do |p| + p.state == "completed" + end + end + + def save_user_address(user) + if user.present? + user.ship_address = self.ship_address if self.ship_address && self.ship_address.valid? + user.bill_address = self.bill_address if self.bill_address && self.bill_address.valid? + end + end + # Indicates whether or not the user is allowed to proceed to checkout. # Currently this is implemented as a check for whether or not there is at # least one LineItem in the Order. Feel free to override this logic in your @@ -221,6 +294,7 @@ def update! end def clone_billing_address + return if bill_address.nil? if bill_address and self.ship_address.nil? self.ship_address = bill_address.clone else @@ -315,7 +389,10 @@ def name end def can_ship? - self.complete? || self.resumed? || self.awaiting_return? || self.returned? + # self.complete? || self.resumed? || self.awaiting_return? || self.returned? + # FR: Fix bug in admin: Shipments are stuck in delivery state + # and cant be shipped + self.delivery? || self.complete? || self.resumed? || self.awaiting_return? || self.returned? end def credit_cards @@ -348,7 +425,11 @@ def finalize! end def deliver_order_confirmation_email - OrderMailer.confirm_email(self.id).deliver + if ENV["ASYNC_EMAILS"] && defined?(Sidekiq) + Spree::OrderMailer.delay.confirm_email(self.id) + else + OrderMailer.confirm_email(self.id).deliver + end update_column(:confirmation_delivered, true) end @@ -648,7 +729,7 @@ def after_resume end def use_billing? - @use_billing == true || @use_billing == 'true' || @use_billing == '1' + self.use_billing == true end def set_currency diff --git a/core/app/models/spree/payment.rb b/core/app/models/spree/payment.rb index e34639a8435..29bfa32a575 100644 --- a/core/app/models/spree/payment.rb +++ b/core/app/models/spree/payment.rb @@ -173,6 +173,8 @@ def profiles_supported? def create_payment_profile return unless source.respond_to?(:has_payment_profile?) && !source.has_payment_profile? + # Imported payments shouldn't create a payment profile. + return if source.imported payment_method.create_profile(self) rescue ActiveMerchant::ConnectionError => e diff --git a/core/app/models/spree/payment/processing.rb b/core/app/models/spree/payment/processing.rb index e9e4671ebc2..7c7113f5120 100644 --- a/core/app/models/spree/payment/processing.rb +++ b/core/app/models/spree/payment/processing.rb @@ -3,7 +3,7 @@ class Payment < Spree::Base module Processing def process! if payment_method && payment_method.source_required? - if source + if source if !processing? if payment_method.supports?(source) || token_based? if payment_method.auto_capture? diff --git a/core/app/models/spree/stock_movement.rb b/core/app/models/spree/stock_movement.rb index 90c6472b5a6..9e3a7fa39d2 100644 --- a/core/app/models/spree/stock_movement.rb +++ b/core/app/models/spree/stock_movement.rb @@ -18,9 +18,18 @@ def readonly? def update_stock_item_quantity return unless self.stock_item.should_track_inventory? + + # FR: Update all backordered orders + begin + self.stock_item.variant.inventory_units.each {|iu| iu.order.update! } + rescue + # just skip this for now + end + stock_item.adjust_count_on_hand quantity end end end + diff --git a/core/lib/spree/core/importer/order.rb b/core/lib/spree/core/importer/order.rb index c42ee699473..3c04e838575 100644 --- a/core/lib/spree/core/importer/order.rb +++ b/core/lib/spree/core/importer/order.rb @@ -143,13 +143,12 @@ def self.create_source_payment_from_params(source_hash, payment) last_digits: source_hash[:last_digits], name: source_hash[:name], payment_method: payment.payment_method, - # Number & Verification value are required attributes - # so just assigning fakes that are dropped when last_digits present. - number: '4111111111111111', - verification_value: '123' + gateway_customer_profile_id: source_hash[:gateway_customer_profile_id], + gateway_payment_profile_id: source_hash[:gateway_customer_profile_id], + imported: true ) rescue Exception => e - raise "Order import source payments: #{e.message} #{s}" + raise "Order import source payments: #{e.message} #{source_hash}" end end diff --git a/core/lib/spree/core/validators/email.rb b/core/lib/spree/core/validators/email.rb index 6882be2cce3..51dd4a447ba 100644 --- a/core/lib/spree/core/validators/email.rb +++ b/core/lib/spree/core/validators/email.rb @@ -1,29 +1,29 @@ -# Borrowed from http://my.rails-royce.org/2010/07/21/email-validation-in-ruby-on-rails-without-regexp/ -# Mentioned in tweet here: https://twitter.com/_sohara/status/177120126083141633 -require 'mail' -class EmailValidator < ActiveModel::EachValidator - def validate_each(record,attribute,value) - unless valid?(value) - record.errors.add(attribute, :invalid, {:value => value}.merge!(options)) - end - end - - def valid?(email) - begin - m = Mail::Address.new(email) - # We must check that value contains a domain and that value is an email address - r = m.domain && m.address == email - t = m.__send__(:tree) - # We need to dig into treetop - # A valid domain must have dot_atom_text elements size > 1 - # user@localhost is excluded - # treetop must respond to domain - # We exclude valid email values like - # Hence we use m.__send__(tree).domain - r &&= (t.domain.dot_atom_text.elements.size > 1) - rescue Exception => e - r = false - end - r - end -end +# # Borrowed from http://my.rails-royce.org/2010/07/21/email-validation-in-ruby-on-rails-without-regexp/ +# # Mentioned in tweet here: https://twitter.com/_sohara/status/177120126083141633 +# require 'mail' +# class EmailValidator < ActiveModel::EachValidator +# def validate_each(record,attribute,value) +# unless valid?(value) +# record.errors.add(attribute, :invalid, {:value => value}.merge!(options)) +# end +# end +# +# def valid?(email) +# begin +# m = Mail::Address.new(email) +# # We must check that value contains a domain and that value is an email address +# r = m.domain && m.address == email +# t = m.__send__(:tree) +# # We need to dig into treetop +# # A valid domain must have dot_atom_text elements size > 1 +# # user@localhost is excluded +# # treetop must respond to domain +# # We exclude valid email values like +# # Hence we use m.__send__(tree).domain +# r &&= (t.domain.dot_atom_text.elements.size > 1) +# rescue Exception => e +# r = false +# end +# r +# end +# end diff --git a/core/spec/models/spree/credit_card_spec.rb b/core/spec/models/spree/credit_card_spec.rb index f3194fc71d6..6972f846858 100644 --- a/core/spec/models/spree/credit_card_spec.rb +++ b/core/spec/models/spree/credit_card_spec.rb @@ -157,6 +157,15 @@ def stub_rails_env(environment) expect(credit_card.errors[:verification_value]).to be_empty end end + + context "imported is true" do + it "does not validate presence of number or cvv" do + credit_card.imported = true + credit_card.valid? + expect(credit_card.errors[:number]).to be_empty + expect(credit_card.errors[:verification_value]).to be_empty + end + end end context "#create" do diff --git a/core/spec/models/spree/payment_spec.rb b/core/spec/models/spree/payment_spec.rb index 61f74844a05..b4d39e07453 100644 --- a/core/spec/models/spree/payment_spec.rb +++ b/core/spec/models/spree/payment_spec.rb @@ -10,8 +10,9 @@ end let(:card) do - mock_model(Spree::CreditCard, :number => "4111111111111111", - :has_payment_profile? => true) + mock_model(Spree::CreditCard, number: "4111111111111111", + has_payment_profile?: true, + imported: false) end let(:payment) do diff --git a/frontend/app/controllers/spree/checkout_controller.rb b/frontend/app/controllers/spree/checkout_controller.rb index d481a22b0cd..17193d2b0c9 100644 --- a/frontend/app/controllers/spree/checkout_controller.rb +++ b/frontend/app/controllers/spree/checkout_controller.rb @@ -6,6 +6,11 @@ module Spree class CheckoutController < Spree::StoreController ssl_required + before_action -> { current_order.update_cart_info if current_order } + + before_action -> { store_location } + before_action -> { save_user_addresses } + before_filter :load_order_with_lock before_filter :ensure_order_not_completed @@ -21,12 +26,15 @@ class CheckoutController < Spree::StoreController helper 'spree/orders' - rescue_from Spree::Core::GatewayError, :with => :rescue_from_spree_gateway_error + rescue_from Spree::Core::GatewayError, :with => :rescue_from_spree_gateway_error + + # Updates the order and advances to the next state (when possible.) def update if @order.update_from_params(params, permitted_checkout_attributes, request.headers.env) @order.temporary_address = !params[:save_user_address] +# unless @order.next flash[:error] = @order.errors.full_messages.join("\n") redirect_to checkout_state_path(@order.state) and return @@ -46,6 +54,24 @@ def update end private + + def save_marketing + if params["order"] && params["order"]["accepts_marketing"] + sub = Formrausch::Subscription.create_from_order(current_order) + if sub.valid? + sub.send_to_campaign_monitor(false, ENV["CAMPAIGN_MONITOR_CHECKOUT_LIST_ID"]) + end + @order.accepts_marketing = true + @order.save + end + end + + def save_user_addresses + if current_order + current_order.save_user_address(spree_current_user) + end + end + def ensure_valid_state unless skip_state_validation? if (params[:state] && !@order.has_checkout_step?(params[:state])) || @@ -107,10 +133,8 @@ def setup_for_current_state end def before_address - # if the user has a default address, a callback takes care of setting - # that; but if he doesn't, we need to build an empty one here - @order.bill_address ||= Address.build_default - @order.ship_address ||= Address.build_default if @order.checkout_steps.include?('delivery') + @order.bill_address ||= Spree::Address.default(try_spree_current_user, "bill") + @order.ship_address ||= Spree::Address.default(try_spree_current_user, "ship") if @order.checkout_steps.include?('delivery') end def before_delivery diff --git a/frontend/app/controllers/spree/content_controller.rb b/frontend/app/controllers/spree/content_controller.rb index d8d104516e1..cc96ce901f9 100644 --- a/frontend/app/controllers/spree/content_controller.rb +++ b/frontend/app/controllers/spree/content_controller.rb @@ -13,7 +13,7 @@ def show end def cvv - render :layout => false + render :layout => "minimal" end def fire_visited_path diff --git a/frontend/app/controllers/spree/orders_controller.rb b/frontend/app/controllers/spree/orders_controller.rb index ba82b3ec78b..24271af8544 100644 --- a/frontend/app/controllers/spree/orders_controller.rb +++ b/frontend/app/controllers/spree/orders_controller.rb @@ -12,6 +12,10 @@ class OrdersController < Spree::StoreController before_filter :apply_coupon_code, only: :update skip_before_filter :verify_authenticity_token + before_action :update_cart_and_adjustments, except: ['show', 'populate'] + + helper Spree::CheckoutHelper + def show @order = Order.find_by_number!(params[:id]) end @@ -44,7 +48,9 @@ def populate populator = Spree::OrderPopulator.new(current_order(create_order_if_necessary: true), current_currency) if populator.populate(params[:variant_id], params[:quantity]) + flash[:notice] = Spree.t(:added_to_cart) respond_with(@order) do |format| + format.js { redirect_to :back } format.html { redirect_to cart_path } end else @@ -82,6 +88,13 @@ def check_authorization private + def update_cart_and_adjustments + if current_order + current_order.state ='cart' + current_order.update_cart_info + end + end + def order_params if params[:order] params[:order].permit(*permitted_order_attributes) diff --git a/spree.gemspec b/spree.gemspec index ddea0e86f50..ec58408d258 100644 --- a/spree.gemspec +++ b/spree.gemspec @@ -25,4 +25,5 @@ Gem::Specification.new do |s| s.add_dependency 'spree_frontend', version s.add_dependency 'spree_sample', version s.add_dependency 'spree_cmd', version + s.add_dependency 'email_validator', "~> 1.4.0" end