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
- #all_adjustments ⇒ Object
- #allow_cancel? ⇒ Boolean
-
#amount ⇒ Object
For compatiblity with Calculator::PriceSack.
- #apply_free_shipping_promotions ⇒ Object
- #approve! ⇒ Object
- #approved? ⇒ Boolean
- #approved_by(user) ⇒ Object
-
#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_add_coupon? ⇒ Boolean
- #can_approve? ⇒ Boolean
- #can_ship? ⇒ Boolean
-
#checkout_allowed? ⇒ Boolean
Indicates whether or not the user is allowed to proceed to checkout.
- #clone_billing_address ⇒ Object
- #completed? ⇒ Boolean
-
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process.
- #consider_risk ⇒ Object
- #considered_risky! ⇒ Object
- #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_additional_tax_total ⇒ Object
- #display_adjustment_total ⇒ Object
- #display_included_tax_total ⇒ Object
- #display_item_total ⇒ Object
- #display_outstanding_balance ⇒ Object
- #display_shipment_total ⇒ Object (also: #display_ship_total)
- #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
FIXME refactor this method and implement validation using validates_* utilities.
- #has_step?(step) ⇒ Boolean
- #insufficient_stock_lines ⇒ Object
- #is_risky? ⇒ Boolean
- #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
- #quantity_of(variant) ⇒ Object
- #refresh_shipment_rates ⇒ Object
- #restart_checkout_flow ⇒ Object
- #set_shipments_cost ⇒ Object
- #shipped? ⇒ Boolean
- #shipped_shipments ⇒ Object
- #shipping_discount ⇒ 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
- #updater ⇒ Object
- #variants ⇒ Object
Methods included from Checkout
Instance Attribute Details
#coupon_code ⇒ Object
Returns the value of attribute coupon_code.
21 22 23 |
# File 'app/models/spree/order.rb', line 21 def coupon_code @coupon_code end |
#use_billing ⇒ Object
Returns the value of attribute use_billing.
68 69 70 |
# File 'app/models/spree/order.rb', line 68 def use_billing @use_billing end |
Class Method Details
.between(start_date, end_date) ⇒ Object
87 88 89 |
# File 'app/models/spree/order.rb', line 87 def self.between(start_date, end_date) where(created_at: 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
83 84 85 |
# File 'app/models/spree/order.rb', line 83 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
#all_adjustments ⇒ Object
113 114 115 |
# File 'app/models/spree/order.rb', line 113 def all_adjustments Adjustment.where("order_id = :order_id OR adjustable_id = :order_id", :order_id => self.id) end |
#allow_cancel? ⇒ Boolean
230 231 232 233 |
# File 'app/models/spree/order.rb', line 230 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
118 119 120 |
# File 'app/models/spree/order.rb', line 118 def amount line_items.inject(0.0) { |sum, li| sum + li.amount } end |
#apply_free_shipping_promotions ⇒ Object
479 480 481 482 483 484 |
# File 'app/models/spree/order.rb', line 479 def apply_free_shipping_promotions Spree::PromotionHandler::FreeShipping.new(self).activate shipments.each { |shipment| ItemAdjustments.new(shipment).update } updater.update_shipment_total persist_totals end |
#approve! ⇒ Object
558 559 560 |
# File 'app/models/spree/order.rb', line 558 def approve! update_column(:considered_risky, false) end |
#approved? ⇒ Boolean
540 541 542 |
# File 'app/models/spree/order.rb', line 540 def approved? !!self.approved_at end |
#approved_by(user) ⇒ Object
529 530 531 532 533 534 535 536 537 538 |
# File 'app/models/spree/order.rb', line 529 def approved_by(user) self.transaction do approve! self.update_columns( approver_id: user.id, approved_at: Time.now, considered_risky: false, ) end end |
#associate_user!(user) ⇒ Object
Associates the specified user with the order.
244 245 246 247 248 249 250 251 252 253 |
# File 'app/models/spree/order.rb', line 244 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
347 348 349 |
# File 'app/models/spree/order.rb', line 347 def available_payment_methods @available_payment_methods ||= (PaymentMethod.available(:front_end) + PaymentMethod.available(:both)).uniq end |
#awaiting_returns? ⇒ Boolean
235 236 237 |
# File 'app/models/spree/order.rb', line 235 def awaiting_returns? .any? { || . } end |
#backordered? ⇒ Boolean
190 191 192 |
# File 'app/models/spree/order.rb', line 190 def backordered? shipments.any?(&:backordered?) end |
#billing_firstname ⇒ Object
387 388 389 |
# File 'app/models/spree/order.rb', line 387 def billing_firstname bill_address.try(:firstname) end |
#billing_lastname ⇒ Object
391 392 393 |
# File 'app/models/spree/order.rb', line 391 def billing_lastname bill_address.try(:lastname) end |
#can_add_coupon? ⇒ Boolean
458 459 460 |
# File 'app/models/spree/order.rb', line 458 def can_add_coupon? Spree::Promotion.order_activatable?(self) end |
#can_approve? ⇒ Boolean
544 545 546 |
# File 'app/models/spree/order.rb', line 544 def can_approve? !approved? end |
#can_ship? ⇒ Boolean
304 305 306 |
# File 'app/models/spree/order.rb', line 304 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.
171 172 173 |
# File 'app/models/spree/order.rb', line 171 def checkout_allowed? line_items.count > 0 end |
#clone_billing_address ⇒ Object
221 222 223 224 225 226 227 228 |
# File 'app/models/spree/order.rb', line 221 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
163 164 165 |
# File 'app/models/spree/order.rb', line 163 def completed? completed_at.present? end |
#confirmation_required? ⇒ Boolean
If true, causes the confirmation step to happen during the checkout process
181 182 183 184 185 186 187 188 |
# File 'app/models/spree/order.rb', line 181 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 |
#consider_risk ⇒ Object
548 549 550 551 552 |
# File 'app/models/spree/order.rb', line 548 def consider_risk if is_risky? && !approved? considered_risky! end end |
#considered_risky! ⇒ Object
554 555 556 |
# File 'app/models/spree/order.rb', line 554 def considered_risky! update_column(:considered_risky, true) end |
#contains?(variant) ⇒ Boolean
270 271 272 |
# File 'app/models/spree/order.rb', line 270 def contains?(variant) find_line_item_by_variant(variant).present? end |
#contents ⇒ Object
239 240 241 |
# File 'app/models/spree/order.rb', line 239 def contents @contents ||= Spree::OrderContents.new(self) end |
#create_proposed_shipments ⇒ Object
467 468 469 470 471 472 473 474 475 476 477 |
# File 'app/models/spree/order.rb', line 467 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.
285 286 287 288 |
# File 'app/models/spree/order.rb', line 285 def create_tax_charge! Spree::TaxRate.adjust(self, line_items) Spree::TaxRate.adjust(self, shipments) if shipments.any? end |
#credit_cards ⇒ Object
308 309 310 311 |
# File 'app/models/spree/order.rb', line 308 def credit_cards credit_card_ids = payments.from_credit_card.pluck(:source_id).uniq CreditCard.where(id: credit_card_ids) end |
#currency ⇒ Object
122 123 124 |
# File 'app/models/spree/order.rb', line 122 def currency self[:currency] || Spree::Config[:currency] end |
#deliver_order_confirmation_email ⇒ Object
337 338 339 340 |
# File 'app/models/spree/order.rb', line 337 def deliver_order_confirmation_email OrderMailer.confirm_email(self.id).deliver update_column(:confirmation_delivered, true) end |
#display_additional_tax_total ⇒ Object
142 143 144 |
# File 'app/models/spree/order.rb', line 142 def display_additional_tax_total Spree::Money.new(additional_tax_total, { currency: currency }) end |
#display_adjustment_total ⇒ Object
134 135 136 |
# File 'app/models/spree/order.rb', line 134 def display_adjustment_total Spree::Money.new(adjustment_total, { currency: currency }) end |
#display_included_tax_total ⇒ Object
138 139 140 |
# File 'app/models/spree/order.rb', line 138 def display_included_tax_total Spree::Money.new(included_tax_total, { currency: currency }) end |
#display_item_total ⇒ Object
130 131 132 |
# File 'app/models/spree/order.rb', line 130 def display_item_total Spree::Money.new(item_total, { currency: currency }) end |
#display_outstanding_balance ⇒ Object
126 127 128 |
# File 'app/models/spree/order.rb', line 126 def display_outstanding_balance Spree::Money.new(outstanding_balance, { currency: currency }) end |
#display_shipment_total ⇒ Object Also known as: display_ship_total
146 147 148 |
# File 'app/models/spree/order.rb', line 146 def display_shipment_total Spree::Money.new(shipment_total, { currency: currency }) end |
#display_total ⇒ Object
151 152 153 |
# File 'app/models/spree/order.rb', line 151 def display_total Spree::Money.new(total, { currency: currency }) end |
#empty! ⇒ Object
427 428 429 430 431 432 |
# File 'app/models/spree/order.rb', line 427 def empty! line_items.destroy_all adjustments.destroy_all update_totals persist_totals 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
491 492 493 494 495 496 497 |
# File 'app/models/spree/order.rb', line 491 def ensure_updated_shipments if shipments.any? self.shipments.destroy_all self.update_column(:shipment_total, 0) 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.
203 204 205 206 |
# File 'app/models/spree/order.rb', line 203 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
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'app/models/spree/order.rb', line 315 def finalize! # lock all adjustments (coupon promotions, etc.) all_adjustments.each{|a| a.close} # 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 touch :completed_at deliver_order_confirmation_email unless confirmation_delivered? consider_risk end |
#find_line_item_by_variant(variant) ⇒ Object
279 280 281 |
# File 'app/models/spree/order.rb', line 279 def find_line_item_by_variant(variant) line_items.detect { |line_item| line_item.variant_id == variant.id } end |
#generate_order_number ⇒ Object
FIXME refactor this method and implement validation using validates_* utilities
256 257 258 259 260 261 262 263 264 |
# File 'app/models/spree/order.rb', line 256 def generate_order_number record = true while record random = "R#{Array.new(9){rand(9)}.join}" record = self.class.where(number: random).first end self.number = random if self.number.blank? self.number end |
#has_step?(step) ⇒ Boolean
434 435 436 |
# File 'app/models/spree/order.rb', line 434 def has_step?(step) checkout_steps.include?(step) end |
#insufficient_stock_lines ⇒ Object
403 404 405 |
# File 'app/models/spree/order.rb', line 403 def insufficient_stock_lines @insufficient_stock_lines ||= line_items.select(&:insufficient_stock?) end |
#is_risky? ⇒ Boolean
520 521 522 523 524 525 526 527 |
# File 'app/models/spree/order.rb', line 520 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 |
#merge!(order, user = nil) ⇒ Object
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'app/models/spree/order.rb', line 407 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
298 299 300 301 302 |
# File 'app/models/spree/order.rb', line 298 def name if (address = bill_address || ship_address) "#{address.firstname} #{address.lastname}" end end |
#outstanding_balance ⇒ Object
290 291 292 |
# File 'app/models/spree/order.rb', line 290 def outstanding_balance total - payment_total end |
#outstanding_balance? ⇒ Boolean
294 295 296 |
# File 'app/models/spree/order.rb', line 294 def outstanding_balance? self.outstanding_balance != 0 end |
#paid? ⇒ Boolean
Helper methods for checkout steps
343 344 345 |
# File 'app/models/spree/order.rb', line 343 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
176 177 178 |
# File 'app/models/spree/order.rb', line 176 def payment_required? total.to_f > 0.0 end |
#pending_payments ⇒ Object
351 352 353 |
# File 'app/models/spree/order.rb', line 351 def pending_payments payments.select { |payment| payment.checkout? || payment.pending? } 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
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'app/models/spree/order.rb', line 368 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
395 396 397 |
# File 'app/models/spree/order.rb', line 395 def products line_items.map(&:product) end |
#quantity_of(variant) ⇒ Object
274 275 276 277 |
# File 'app/models/spree/order.rb', line 274 def quantity_of(variant) line_item = find_line_item_by_variant(variant) line_item ? line_item.quantity : 0 end |
#refresh_shipment_rates ⇒ Object
506 507 508 |
# File 'app/models/spree/order.rb', line 506 def refresh_shipment_rates shipments.map &:refresh_rates end |
#restart_checkout_flow ⇒ Object
499 500 501 502 503 504 |
# File 'app/models/spree/order.rb', line 499 def restart_checkout_flow self.update_columns( state: checkout_steps.first, updated_at: Time.now, ) end |
#set_shipments_cost ⇒ Object
514 515 516 517 518 |
# File 'app/models/spree/order.rb', line 514 def set_shipments_cost shipments.each(&:update_amounts) updater.update_shipment_total persist_totals end |
#shipped? ⇒ Boolean
463 464 465 |
# File 'app/models/spree/order.rb', line 463 def shipped? %w(partial shipped).include?(shipment_state) end |
#shipped_shipments ⇒ Object
266 267 268 |
# File 'app/models/spree/order.rb', line 266 def shipped_shipments shipments.shipped end |
#shipping_discount ⇒ Object
155 156 157 |
# File 'app/models/spree/order.rb', line 155 def shipping_discount shipment_adjustments.eligible.sum(:amount) * - 1 end |
#shipping_eq_billing_address? ⇒ Boolean
510 511 512 |
# File 'app/models/spree/order.rb', line 510 def shipping_eq_billing_address? (bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address) end |
#state_changed(name) ⇒ Object
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'app/models/spree/order.rb', line 438 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
209 210 211 |
# File 'app/models/spree/order.rb', line 209 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
196 197 198 |
# File 'app/models/spree/order.rb', line 196 def tax_zone Zone.match(tax_address) || Zone.default_tax end |
#to_param ⇒ Object
159 160 161 |
# File 'app/models/spree/order.rb', line 159 def to_param number.to_s.to_url.upcase end |
#update! ⇒ Object
217 218 219 |
# File 'app/models/spree/order.rb', line 217 def update! updater.update end |
#updater ⇒ Object
213 214 215 |
# File 'app/models/spree/order.rb', line 213 def updater @updater ||= OrderUpdater.new(self) end |
#variants ⇒ Object
399 400 401 |
# File 'app/models/spree/order.rb', line 399 def variants line_items.map(&:variant) end |