Class: Spree::Order
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Spree::Order
- Includes:
- Checkout
- Defined in:
- app/models/spree/order.rb,
app/models/spree/order/checkout.rb
Defined Under Namespace
Modules: Checkout
Instance Attribute Summary collapse
-
#coupon_code ⇒ Object
Returns the value of attribute coupon_code.
-
#use_billing ⇒ Object
Returns the value of attribute use_billing.
Class Method Summary collapse
- .between(start_date, end_date) ⇒ Object
- .by_customer(customer) ⇒ Object
- .by_number(number) ⇒ Object
- .by_state(state) ⇒ Object
- .complete ⇒ Object
- .incomplete ⇒ Object
-
.register_update_hook(hook) ⇒ Object
Use this method in other gems that wish to register their own custom logic that should be called after Order#update.
Instance Method Summary collapse
- #add_variant(variant, quantity = 1, currency = nil) ⇒ Object
- #allow_cancel? ⇒ Boolean
-
#amount ⇒ Object
For compatiblity with Calculator::PriceSack.
-
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
- #available_payment_methods ⇒ Object
- #awaiting_returns? ⇒ Boolean
- #backordered? ⇒ Boolean
- #billing_firstname ⇒ Object
- #billing_lastname ⇒ Object
- #can_ship? ⇒ Boolean
-
#checkout_allowed? ⇒ Boolean
Indicates whether or not the user is allowed to proceed to checkout.
- #clear_adjustments! ⇒ Object
- #clone_billing_address ⇒ Object
- #completed? ⇒ Boolean
-
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process.
- #contains?(variant) ⇒ Boolean
- #contents ⇒ Object
- #create_proposed_shipments ⇒ Object
-
#create_tax_charge! ⇒ Object
Creates new tax charges if there are any applicable rates.
- #credit_cards ⇒ Object
- #currency ⇒ Object
- #deliver_order_confirmation_email ⇒ Object
- #display_adjustment_total ⇒ Object
- #display_item_total ⇒ Object
- #display_outstanding_balance ⇒ Object
- #display_ship_total ⇒ Object
- #display_tax_total ⇒ Object
- #display_total ⇒ Object
- #empty! ⇒ Object
-
#ensure_updated_shipments ⇒ Object
Clean shipments and make order back to address state.
-
#exclude_tax? ⇒ Boolean
Indicates whether tax should be backed out of the price calcualtions in cases where prices include tax but the customer is not required to pay taxes in that case.
-
#finalize! ⇒ Object
Finalizes an in progress order after checkout is complete.
- #find_line_item_by_variant(variant) ⇒ Object
- #generate_order_number ⇒ Object
- #has_step?(step) ⇒ Boolean
- #insufficient_stock_lines ⇒ Object
-
#item_count ⇒ Object
Indicates the number of items in the order.
-
#line_item_adjustment_totals ⇒ Object
Array of totals grouped by Adjustment#label.
- #merge!(order, user = nil) ⇒ Object
- #name ⇒ Object
- #outstanding_balance ⇒ Object
- #outstanding_balance? ⇒ Boolean
-
#paid? ⇒ Boolean
Helper methods for checkout steps.
-
#payment_required? ⇒ Boolean
Is this a free order in which case the payment step should be skipped.
- #pending_payments ⇒ Object
- #price_adjustment_totals ⇒ Object
- #price_adjustments ⇒ Object
-
#process_payments! ⇒ Object
processes any pending payments and must return a boolean as it’s return value is used by the checkout state_machine to determine success or failure of the ‘complete’ event for the order.
- #products ⇒ Object
- #promo_total ⇒ Object
-
#promotion_credit_exists?(originator) ⇒ Boolean
Tells us if there if the specified promotion is already associated with the order regardless of whether or not its currently eligible.
- #quantity_of(variant) ⇒ Object
- #refresh_shipment_rates ⇒ Object
-
#remove_invalid_shipments! ⇒ Object
Clear shipment when transitioning to delivery step of checkout if the current shipping address is not eligible for the existing shipping method.
- #remove_variant(variant, quantity = 1) ⇒ Object
- #ship_total ⇒ Object
- #shipment ⇒ Object
- #shipped? ⇒ Boolean
- #shipped_shipments ⇒ Object
- #state_changed(name) ⇒ Object
- #tax_total ⇒ Object
-
#tax_zone ⇒ Object
Returns the relevant zone (if any) to be used for taxation purposes.
- #to_param ⇒ Object
- #update! ⇒ Object
- #update_totals ⇒ Object
- #updater ⇒ Object
- #variants ⇒ Object
Methods included from Checkout
Instance Attribute Details
#coupon_code ⇒ Object
Returns the value of attribute coupon_code.
34 35 36 |
# File 'app/models/spree/order.rb', line 34 def coupon_code @coupon_code end |
#use_billing ⇒ Object
Returns the value of attribute use_billing.
74 75 76 |
# File 'app/models/spree/order.rb', line 74 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
97 98 99 100 |
# File 'app/models/spree/order.rb', line 97 def self.between(start_date, end_date) ActiveSupport::Deprecation.warn("Order#between will be deprecated in Spree 2.3, please use either Order#created_between or Order#completed_between instead.") self.created_between(start_date, end_date) end |
.by_customer(customer) ⇒ Object
102 103 104 |
# File 'app/models/spree/order.rb', line 102 def self.by_customer(customer) joins(:user).where("#{Spree.user_class.table_name}.email" => customer) end |
.by_number(number) ⇒ Object
90 91 92 |
# File 'app/models/spree/order.rb', line 90 def self.by_number(number) where(number: number) end |
.by_state(state) ⇒ Object
106 107 108 |
# File 'app/models/spree/order.rb', line 106 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
110 111 112 |
# File 'app/models/spree/order.rb', line 110 def self.complete where("#{quoted_table_name}.completed_at IS NOT NULL") end |
.incomplete ⇒ Object
114 115 116 |
# File 'app/models/spree/order.rb', line 114 def self.incomplete where(completed_at: nil) end |
.register_update_hook(hook) ⇒ Object
Use this method in other gems that wish to register their own custom logic that should be called after Order#update
120 121 122 |
# File 'app/models/spree/order.rb', line 120 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#add_variant(variant, quantity = 1, currency = nil) ⇒ Object
266 267 268 269 270 |
# File 'app/models/spree/order.rb', line 266 def add_variant(variant, quantity = 1, currency = nil) ActiveSupport::Deprecation.warn("[SPREE] Spree::Order#add_variant will be deprecated in Spree 2.1, please use order.contents.add instead.") contents.currency = currency unless currency.nil? contents.add(variant, quantity) end |
#allow_cancel? ⇒ Boolean
253 254 255 256 |
# File 'app/models/spree/order.rb', line 253 def allow_cancel? return false unless completed? and state != 'canceled' shipment_state.nil? || %w{ready backorder pending}.include?(shipment_state) end |
#amount ⇒ Object
For compatiblity with Calculator::PriceSack
125 126 127 |
# File 'app/models/spree/order.rb', line 125 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
279 280 281 282 283 284 285 286 287 288 |
# File 'app/models/spree/order.rb', line 279 def associate_user!(user) self.user = user self.email = user.email self.created_by = user if self.created_by.blank? if persisted? # immediately persist the changes we just made, but don't use save since we might have an invalid address associated self.class.unscoped.where(id: id).update_all(email: user.email, user_id: user.id, created_by_id: self.created_by_id) end end |
#available_payment_methods ⇒ Object
405 406 407 |
# File 'app/models/spree/order.rb', line 405 def available_payment_methods @available_payment_methods ||= (PaymentMethod.available(:front_end) + PaymentMethod.available(:both)).uniq end |
#awaiting_returns? ⇒ Boolean
258 259 260 |
# File 'app/models/spree/order.rb', line 258 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
193 194 195 |
# File 'app/models/spree/order.rb', line 193 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
445 446 447 |
# File 'app/models/spree/order.rb', line 445 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
449 450 451 |
# File 'app/models/spree/order.rb', line 449 def billing_lastname bill_address.try(:lastname) end |
#can_ship? ⇒ Boolean
353 354 355 |
# File 'app/models/spree/order.rb', line 353 def can_ship? self.complete? || self.resumed? || self.awaiting_return? || self.returned? end |
#checkout_allowed? ⇒ Boolean
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 own application if you require additional steps before allowing a checkout.
169 170 171 |
# File 'app/models/spree/order.rb', line 169 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
490 491 492 493 |
# File 'app/models/spree/order.rb', line 490 def clear_adjustments! self.adjustments.destroy_all self.line_item_adjustments.destroy_all end |
#clone_billing_address ⇒ Object
244 245 246 247 248 249 250 251 |
# File 'app/models/spree/order.rb', line 244 def clone_billing_address if bill_address and self.ship_address.nil? self.ship_address = bill_address.clone else self.ship_address.attributes = bill_address.attributes.except('id', 'updated_at', 'created_at') end true end |
#completed? ⇒ Boolean
161 162 163 |
# File 'app/models/spree/order.rb', line 161 def completed? completed_at.present? || complete? end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
179 180 181 182 183 184 185 186 |
# File 'app/models/spree/order.rb', line 179 def confirmation_required? Spree::Config[:always_include_confirm_step] || payments.valid.map(&:payment_method).compact.any?(&:payment_profiles_supported?) || # Little hacky fix for #4117 # If this wasn't here, order would transition to address state on confirm failure # because there would be no valid payments any more. state == 'confirm' end |
#contains?(variant) ⇒ Boolean
306 307 308 |
# File 'app/models/spree/order.rb', line 306 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
262 263 264 |
# File 'app/models/spree/order.rb', line 262 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
537 538 539 540 541 542 543 544 545 546 547 |
# File 'app/models/spree/order.rb', line 537 def create_proposed_shipments adjustments.shipping.delete_all shipments.destroy_all packages = Spree::Stock::Coordinator.new(self).packages packages.each do |package| shipments << package.to_shipment end shipments end |
#create_tax_charge! ⇒ Object
Creates new tax charges if there are any applicable rates. If prices already include taxes then price adjustments are created instead.
335 336 337 |
# File 'app/models/spree/order.rb', line 335 def create_tax_charge! Spree::TaxRate.adjust(self) end |
#credit_cards ⇒ Object
357 358 359 360 |
# File 'app/models/spree/order.rb', line 357 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.scoped(conditions: { id: credit_card_ids }) end |
#currency ⇒ Object
129 130 131 |
# File 'app/models/spree/order.rb', line 129 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
391 392 393 394 395 396 397 398 |
# File 'app/models/spree/order.rb', line 391 def deliver_order_confirmation_email begin OrderMailer.confirm_email(self.id).deliver rescue Exception => e logger.error("#{e.class.name}: #{e.}") logger.error(e.backtrace * "\n") end end |
#display_adjustment_total ⇒ Object
141 142 143 |
# File 'app/models/spree/order.rb', line 141 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
137 138 139 |
# File 'app/models/spree/order.rb', line 137 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
133 134 135 |
# File 'app/models/spree/order.rb', line 133 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
149 150 151 |
# File 'app/models/spree/order.rb', line 149 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
145 146 147 |
# File 'app/models/spree/order.rb', line 145 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
153 154 155 |
# File 'app/models/spree/order.rb', line 153 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
485 486 487 488 |
# File 'app/models/spree/order.rb', line 485 def empty! adjustments.destroy_all line_items.destroy_all end |
#ensure_updated_shipments ⇒ Object
Clean shipments and make order back to address state
At some point the might need to force the order to transition from address to delivery again so that proper updated shipments are created. e.g. customer goes back from payment step and changes order items
554 555 556 557 558 559 |
# File 'app/models/spree/order.rb', line 554 def ensure_updated_shipments if shipments.any? self.shipments.destroy_all self.update_column(:state, "address") end end |
#exclude_tax? ⇒ Boolean
Indicates whether tax should be backed out of the price calcualtions in cases where prices include tax but the customer is not required to pay taxes in that case.
207 208 209 210 |
# File 'app/models/spree/order.rb', line 207 def exclude_tax? return false unless Spree::Config[:prices_inc_tax] return tax_zone != Zone.default_tax end |
#finalize! ⇒ Object
Finalizes an in progress order after checkout is complete. Called after transition to complete state when payments will have been processed
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 |
# File 'app/models/spree/order.rb', line 364 def finalize! touch :completed_at # lock all adjustments (coupon promotions, etc.) adjustments.update_all state: 'closed' # update payment and shipment(s) states, and save updater.update_payment_state shipments.each do |shipment| shipment.update!(self) shipment.finalize! end updater.update_shipment_state save updater.run_hooks deliver_order_confirmation_email self.state_changes.create({ previous_state: 'cart', next_state: 'complete', name: 'order' , user_id: self.user_id }, without_protection: true) end |
#find_line_item_by_variant(variant) ⇒ Object
315 316 317 |
# File 'app/models/spree/order.rb', line 315 def find_line_item_by_variant(variant) line_items.detect { |line_item| line_item.variant_id == variant.id } end |
#generate_order_number ⇒ Object
290 291 292 293 294 295 |
# File 'app/models/spree/order.rb', line 290 def generate_order_number self.number ||= loop do random = "R#{Array.new(9){rand(9)}.join}" break random unless self.class.exists?(number: random) end end |
#has_step?(step) ⇒ Boolean
495 496 497 |
# File 'app/models/spree/order.rb', line 495 def has_step?(step) checkout_steps.include?(step) end |
#insufficient_stock_lines ⇒ Object
461 462 463 |
# File 'app/models/spree/order.rb', line 461 def insufficient_stock_lines line_items.select(&:insufficient_stock?) end |
#item_count ⇒ Object
Indicates the number of items in the order
189 190 191 |
# File 'app/models/spree/order.rb', line 189 def item_count line_items.inject(0) { |sum, li| sum + li.quantity } end |
#line_item_adjustment_totals ⇒ Object
Array of totals grouped by Adjustment#label. Useful for displaying line item adjustments on an invoice. For example, you can display tax breakout for cases where tax is included in price.
220 221 222 223 224 225 |
# File 'app/models/spree/order.rb', line 220 def line_item_adjustment_totals Hash[self.line_item_adjustments.eligible.group_by(&:label).map do |label, adjustments| total = adjustments.sum(&:amount) [label, Spree::Money.new(total, { currency: currency })] end] end |
#merge!(order, user = nil) ⇒ Object
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 |
# File 'app/models/spree/order.rb', line 465 def merge!(order, user = nil) order.line_items.each do |line_item| next unless line_item.currency == currency current_line_item = self.line_items.find_by_variant_id(line_item.variant_id) if current_line_item current_line_item.quantity += line_item.quantity current_line_item.save else line_item.order_id = self.id line_item.save end end self.associate_user!(user) if !self.user && !user.blank? # So that the destroy doesn't take out line items which may have been re-assigned order.line_items.reload order.destroy end |
#name ⇒ Object
347 348 349 350 351 |
# File 'app/models/spree/order.rb', line 347 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
339 340 341 |
# File 'app/models/spree/order.rb', line 339 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
343 344 345 |
# File 'app/models/spree/order.rb', line 343 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
401 402 403 |
# File 'app/models/spree/order.rb', line 401 def paid? payment_state == 'paid' || payment_state == 'credit_owed' end |
#payment_required? ⇒ Boolean
Is this a free order in which case the payment step should be skipped
174 175 176 |
# File 'app/models/spree/order.rb', line 174 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
409 410 411 |
# File 'app/models/spree/order.rb', line 409 def pending_payments payments.select(&:checkout?) end |
#price_adjustment_totals ⇒ Object
227 228 229 230 |
# File 'app/models/spree/order.rb', line 227 def price_adjustment_totals ActiveSupport::Deprecation.warn("Order#price_adjustment_totals will be deprecated in Spree 2.1, please use Order#line_item_adjustment_totals instead.") self.line_item_adjustment_totals end |
#price_adjustments ⇒ Object
212 213 214 215 |
# File 'app/models/spree/order.rb', line 212 def price_adjustments ActiveSupport::Deprecation.warn("Order#price_adjustments will be deprecated in Spree 2.1, please use Order#line_item_adjustments instead.") self.line_item_adjustments end |
#process_payments! ⇒ Object
processes any pending payments and must return a boolean as it’s return value is used by the checkout state_machine to determine success or failure of the ‘complete’ event for the order
Returns:
-
true if all pending_payments processed successfully
-
true if a payment failed, ie. raised a GatewayError which gets rescued and converted to TRUE when :allow_checkout_gateway_error is set to true
-
false if a payment failed, ie. raised a GatewayError which gets rescued and converted to FALSE when :allow_checkout_on_gateway_error is set to false
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 |
# File 'app/models/spree/order.rb', line 426 def process_payments! if pending_payments.empty? raise Core::GatewayError.new Spree.t(:no_pending_payments) else pending_payments.each do |payment| break if payment_total >= total payment.process! if payment.completed? self.payment_total += payment.amount end end end rescue Core::GatewayError => e result = !!Spree::Config[:allow_checkout_on_gateway_error] errors.add(:base, e.) and return result end |
#products ⇒ Object
453 454 455 |
# File 'app/models/spree/order.rb', line 453 def products line_items.map(&:product) end |
#promo_total ⇒ Object
529 530 531 |
# File 'app/models/spree/order.rb', line 529 def promo_total adjustments.eligible.promotion.map(&:amount).sum end |
#promotion_credit_exists?(originator) ⇒ Boolean
Tells us if there if the specified promotion is already associated with the order regardless of whether or not its currently eligible. Useful because generally you would only want a promotion action to apply to order no more than once.
Receives an adjustment originator
(here a PromotionAction object) and tells if the order has adjustments from that already
525 526 527 |
# File 'app/models/spree/order.rb', line 525 def promotion_credit_exists?(originator) !! adjustments.includes(:originator).promotion.reload.detect { |credit| credit.originator.id == originator.id } end |
#quantity_of(variant) ⇒ Object
310 311 312 313 |
# File 'app/models/spree/order.rb', line 310 def quantity_of(variant) line_item = find_line_item_by_variant(variant) line_item ? line_item.quantity : 0 end |
#refresh_shipment_rates ⇒ Object
561 562 563 |
# File 'app/models/spree/order.rb', line 561 def refresh_shipment_rates shipments.map &:refresh_rates end |
#remove_invalid_shipments! ⇒ Object
Clear shipment when transitioning to delivery step of checkout if the current shipping address is not eligible for the existing shipping method
329 330 331 |
# File 'app/models/spree/order.rb', line 329 def remove_invalid_shipments! shipments.each { |s| s.destroy unless s.shipping_method.available_to_order?(self) } end |
#remove_variant(variant, quantity = 1) ⇒ Object
273 274 275 276 |
# File 'app/models/spree/order.rb', line 273 def remove_variant(variant, quantity = 1) ActiveSupport::Deprecation.warn("[SPREE] Spree::Order#remove_variant will be deprecated in Spree 2.1, please use order.contents.remove instead.") contents.remove(variant, quantity) end |
#ship_total ⇒ Object
319 320 321 |
# File 'app/models/spree/order.rb', line 319 def ship_total adjustments.shipping.map(&:amount).sum end |
#shipment ⇒ Object
297 298 299 300 |
# File 'app/models/spree/order.rb', line 297 def shipment ActiveSupport::Deprecation.warn("[SPREE] Spree::Order#shipment is typically incorrect due to multiple shipments and will be deprecated in Spree 2.1, please process Spree::Order#shipments instead.") @shipment ||= shipments.last end |
#shipped? ⇒ Boolean
533 534 535 |
# File 'app/models/spree/order.rb', line 533 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
302 303 304 |
# File 'app/models/spree/order.rb', line 302 def shipped_shipments shipments.shipped end |
#state_changed(name) ⇒ Object
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 |
# File 'app/models/spree/order.rb', line 499 def state_changed(name) state = "#{name}_state" if persisted? old_state = self.send("#{state}_was") new_state = self.send(state) unless old_state == new_state self.state_changes.create({ previous_state: old_state, next_state: new_state, name: name, user_id: self.user_id }, :without_protection => true) end end end |
#tax_total ⇒ Object
323 324 325 |
# File 'app/models/spree/order.rb', line 323 def tax_total adjustments.tax.map(&:amount).sum end |
#tax_zone ⇒ Object
Returns the relevant zone (if any) to be used for taxation purposes. Uses default tax zone unless there is a specific match
199 200 201 202 |
# File 'app/models/spree/order.rb', line 199 def tax_zone zone_address = Spree::Config[:tax_using_ship_address] ? ship_address : bill_address Zone.match(zone_address) || Zone.default_tax end |
#to_param ⇒ Object
157 158 159 |
# File 'app/models/spree/order.rb', line 157 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
236 237 238 |
# File 'app/models/spree/order.rb', line 236 def update! updater.update end |
#update_totals ⇒ Object
240 241 242 |
# File 'app/models/spree/order.rb', line 240 def update_totals updater.update_totals end |
#updater ⇒ Object
232 233 234 |
# File 'app/models/spree/order.rb', line 232 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
457 458 459 |
# File 'app/models/spree/order.rb', line 457 def variants line_items.map(&:variant) end |