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
- #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
- #discount_total ⇒ 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
- #is_risky? ⇒ Boolean
-
#item_count ⇒ Object
Indicates the number of items in the order.
-
#line_item_adjustment_totals ⇒ Object
Array of totals grouped by Adjustment#label.
- #manual_adjustment_total ⇒ Object
- #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
-
#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
- #restart_checkout_flow ⇒ Object
- #ship_total ⇒ Object
- #shipped? ⇒ Boolean
- #shipped_shipments ⇒ Object
- #shipping_eq_billing_address? ⇒ Boolean
- #state_changed(name) ⇒ Object
-
#tax_address ⇒ Object
Returns the address for taxation based on configuration.
-
#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.
22 23 24 |
# File 'app/models/spree/order.rb', line 22 def coupon_code @coupon_code end |
#use_billing ⇒ Object
Returns the value of attribute use_billing.
63 64 65 |
# File 'app/models/spree/order.rb', line 63 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
86 87 88 89 |
# File 'app/models/spree/order.rb', line 86 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
91 92 93 |
# File 'app/models/spree/order.rb', line 91 def self.by_customer(customer) joins(:user).where("#{Spree.user_class.table_name}.email" => customer) end |
.by_number(number) ⇒ Object
79 80 81 |
# File 'app/models/spree/order.rb', line 79 def self.by_number(number) where(number: number) end |
.by_state(state) ⇒ Object
95 96 97 |
# File 'app/models/spree/order.rb', line 95 def self.by_state(state) where(state: state) end |
.complete ⇒ Object
99 100 101 |
# File 'app/models/spree/order.rb', line 99 def self.complete where.not(completed_at: nil) end |
.incomplete ⇒ Object
103 104 105 |
# File 'app/models/spree/order.rb', line 103 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
109 110 111 |
# File 'app/models/spree/order.rb', line 109 def self.register_update_hook(hook) self.update_hooks.add(hook) end |
Instance Method Details
#allow_cancel? ⇒ Boolean
236 237 238 239 |
# File 'app/models/spree/order.rb', line 236 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
114 115 116 |
# File 'app/models/spree/order.rb', line 114 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
250 251 252 253 254 255 256 257 258 259 |
# File 'app/models/spree/order.rb', line 250 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
354 355 356 |
# File 'app/models/spree/order.rb', line 354 def available_payment_methods @available_payment_methods ||= (PaymentMethod.available(:front_end) + PaymentMethod.available(:both)).uniq end |
#awaiting_returns? ⇒ Boolean
241 242 243 |
# File 'app/models/spree/order.rb', line 241 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
182 183 184 |
# File 'app/models/spree/order.rb', line 182 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
394 395 396 |
# File 'app/models/spree/order.rb', line 394 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
398 399 400 |
# File 'app/models/spree/order.rb', line 398 def billing_lastname bill_address.try(:lastname) end |
#can_ship? ⇒ Boolean
309 310 311 |
# File 'app/models/spree/order.rb', line 309 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.
158 159 160 |
# File 'app/models/spree/order.rb', line 158 def checkout_allowed? line_items.count > 0 end |
#clear_adjustments! ⇒ Object
439 440 441 442 |
# File 'app/models/spree/order.rb', line 439 def clear_adjustments! self.adjustments.destroy_all self.line_item_adjustments.destroy_all end |
#clone_billing_address ⇒ Object
227 228 229 230 231 232 233 234 |
# File 'app/models/spree/order.rb', line 227 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
150 151 152 |
# File 'app/models/spree/order.rb', line 150 def completed? completed_at.present? || complete? end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
168 169 170 171 172 173 174 175 |
# File 'app/models/spree/order.rb', line 168 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
272 273 274 |
# File 'app/models/spree/order.rb', line 272 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
245 246 247 |
# File 'app/models/spree/order.rb', line 245 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
494 495 496 497 498 499 500 501 502 503 504 |
# File 'app/models/spree/order.rb', line 494 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.
291 292 293 |
# File 'app/models/spree/order.rb', line 291 def create_tax_charge! Spree::TaxRate.adjust(self) end |
#credit_cards ⇒ Object
313 314 315 316 |
# File 'app/models/spree/order.rb', line 313 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.where(id: credit_card_ids) end |
#currency ⇒ Object
118 119 120 |
# File 'app/models/spree/order.rb', line 118 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
340 341 342 343 344 345 346 347 |
# File 'app/models/spree/order.rb', line 340 def deliver_order_confirmation_email begin OrderMailer.confirm_email(self.id).deliver rescue Exception => e logger.error("#{e.class.name}: #{e.message}") logger.error(e.backtrace * "\n") end end |
#discount_total ⇒ Object
486 487 488 |
# File 'app/models/spree/order.rb', line 486 def discount_total promo_total + manual_adjustment_total end |
#display_adjustment_total ⇒ Object
130 131 132 |
# File 'app/models/spree/order.rb', line 130 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_item_total ⇒ Object
126 127 128 |
# File 'app/models/spree/order.rb', line 126 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
122 123 124 |
# File 'app/models/spree/order.rb', line 122 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_ship_total ⇒ Object
138 139 140 |
# File 'app/models/spree/order.rb', line 138 def display_ship_total Spree::Money.new(ship_total, { currency: currency }) end |
#display_tax_total ⇒ Object
134 135 136 |
# File 'app/models/spree/order.rb', line 134 def display_tax_total Spree::Money.new(tax_total, { currency: currency }) end |
#display_total ⇒ Object
142 143 144 |
# File 'app/models/spree/order.rb', line 142 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
434 435 436 437 |
# File 'app/models/spree/order.rb', line 434 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
511 512 513 514 515 516 |
# File 'app/models/spree/order.rb', line 511 def ensure_updated_shipments if shipments.any? && !self.completed? self.shipments.destroy_all restart_checkout_flow 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.
195 196 197 198 |
# File 'app/models/spree/order.rb', line 195 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
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'app/models/spree/order.rb', line 320 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 end |
#find_line_item_by_variant(variant) ⇒ Object
281 282 283 |
# File 'app/models/spree/order.rb', line 281 def find_line_item_by_variant(variant) line_items.detect { |line_item| line_item.variant_id == variant.id } end |
#generate_order_number ⇒ Object
261 262 263 264 265 266 |
# File 'app/models/spree/order.rb', line 261 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
444 445 446 |
# File 'app/models/spree/order.rb', line 444 def has_step?(step) checkout_steps.include?(step) end |
#insufficient_stock_lines ⇒ Object
410 411 412 |
# File 'app/models/spree/order.rb', line 410 def insufficient_stock_lines line_items.select &:insufficient_stock? end |
#is_risky? ⇒ Boolean
530 531 532 533 534 535 536 537 |
# File 'app/models/spree/order.rb', line 530 def is_risky? self.payments.where(%{ (avs_response IS NOT NULL and avs_response != '' and avs_response != 'D' and avs_response != 'M') or (cvv_response_code IS NOT NULL and cvv_response_code != 'M') or cvv_response_message IS NOT NULL and cvv_response_message != '' or state = 'failed' }.squish!).uniq.count > 0 end |
#item_count ⇒ Object
Indicates the number of items in the order
178 179 180 |
# File 'app/models/spree/order.rb', line 178 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.
208 209 210 211 212 213 |
# File 'app/models/spree/order.rb', line 208 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 |
#manual_adjustment_total ⇒ Object
482 483 484 |
# File 'app/models/spree/order.rb', line 482 def manual_adjustment_total adjustments.eligible.manual.sum(:amount) end |
#merge!(order, user = nil) ⇒ Object
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 |
# File 'app/models/spree/order.rb', line 414 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: line_item.variant) 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
303 304 305 306 307 |
# File 'app/models/spree/order.rb', line 303 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
295 296 297 |
# File 'app/models/spree/order.rb', line 295 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
299 300 301 |
# File 'app/models/spree/order.rb', line 299 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
350 351 352 |
# File 'app/models/spree/order.rb', line 350 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
163 164 165 |
# File 'app/models/spree/order.rb', line 163 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
358 359 360 |
# File 'app/models/spree/order.rb', line 358 def pending_payments payments.select(&:checkout?) 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
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 |
# File 'app/models/spree/order.rb', line 375 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
402 403 404 |
# File 'app/models/spree/order.rb', line 402 def products line_items.map(&:product) end |
#promo_total ⇒ Object
478 479 480 |
# File 'app/models/spree/order.rb', line 478 def promo_total adjustments.eligible.promotion.sum(:amount) 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
474 475 476 |
# File 'app/models/spree/order.rb', line 474 def promotion_credit_exists?(originator) !! adjustments.includes(:originator).promotion.reload.detect { |credit| credit.originator.id == originator.id } end |
#quantity_of(variant) ⇒ Object
276 277 278 279 |
# File 'app/models/spree/order.rb', line 276 def quantity_of(variant) line_item = find_line_item_by_variant(variant) line_item ? line_item.quantity : 0 end |
#refresh_shipment_rates ⇒ Object
522 523 524 |
# File 'app/models/spree/order.rb', line 522 def refresh_shipment_rates shipments.map &:refresh_rates end |
#restart_checkout_flow ⇒ Object
518 519 520 |
# File 'app/models/spree/order.rb', line 518 def restart_checkout_flow self.update_column(:state, checkout_steps.first) end |
#ship_total ⇒ Object
285 286 287 |
# File 'app/models/spree/order.rb', line 285 def ship_total adjustments.shipping.sum(:amount) end |
#shipped? ⇒ Boolean
490 491 492 |
# File 'app/models/spree/order.rb', line 490 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
268 269 270 |
# File 'app/models/spree/order.rb', line 268 def shipped_shipments shipments.shipped end |
#shipping_eq_billing_address? ⇒ Boolean
526 527 528 |
# File 'app/models/spree/order.rb', line 526 def shipping_eq_billing_address? (bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address) end |
#state_changed(name) ⇒ Object
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'app/models/spree/order.rb', line 448 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 ) end end end |
#tax_address ⇒ Object
Returns the address for taxation based on configuration
201 202 203 |
# File 'app/models/spree/order.rb', line 201 def tax_address Spree::Config[:tax_using_ship_address] ? ship_address : bill_address 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
188 189 190 |
# File 'app/models/spree/order.rb', line 188 def tax_zone Zone.match(tax_address) || Zone.default_tax end |
#to_param ⇒ Object
146 147 148 |
# File 'app/models/spree/order.rb', line 146 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
219 220 221 |
# File 'app/models/spree/order.rb', line 219 def update! updater.update end |
#update_totals ⇒ Object
223 224 225 |
# File 'app/models/spree/order.rb', line 223 def update_totals updater.update_totals end |
#updater ⇒ Object
215 216 217 |
# File 'app/models/spree/order.rb', line 215 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
406 407 408 |
# File 'app/models/spree/order.rb', line 406 def variants line_items.map(&:variant) end |