effective_orders 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1004 -0
  4. data/app/assets/images/effective_orders/stripe.png +0 -0
  5. data/app/assets/javascripts/effective_orders.js +6 -0
  6. data/app/assets/javascripts/effective_orders/customers.js.coffee +32 -0
  7. data/app/assets/javascripts/effective_orders/providers/stripe.js.coffee +77 -0
  8. data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +81 -0
  9. data/app/assets/stylesheets/effective_orders.scss +2 -0
  10. data/app/assets/stylesheets/effective_orders/_cart.scss +4 -0
  11. data/app/assets/stylesheets/effective_orders/_order.scss +58 -0
  12. data/app/controllers/admin/customers_controller.rb +24 -0
  13. data/app/controllers/admin/order_items_controller.rb +16 -0
  14. data/app/controllers/admin/orders_controller.rb +223 -0
  15. data/app/controllers/effective/carts_controller.rb +85 -0
  16. data/app/controllers/effective/concerns/purchase.rb +62 -0
  17. data/app/controllers/effective/customers_controller.rb +20 -0
  18. data/app/controllers/effective/orders_controller.rb +162 -0
  19. data/app/controllers/effective/providers/cheque.rb +22 -0
  20. data/app/controllers/effective/providers/free.rb +33 -0
  21. data/app/controllers/effective/providers/mark_as_paid.rb +33 -0
  22. data/app/controllers/effective/providers/moneris.rb +60 -0
  23. data/app/controllers/effective/providers/paypal.rb +33 -0
  24. data/app/controllers/effective/providers/phone.rb +22 -0
  25. data/app/controllers/effective/providers/pretend.rb +26 -0
  26. data/app/controllers/effective/providers/refund.rb +33 -0
  27. data/app/controllers/effective/providers/stripe.rb +72 -0
  28. data/app/controllers/effective/subscripter_controller.rb +18 -0
  29. data/app/controllers/effective/webhooks_controller.rb +109 -0
  30. data/app/datatables/admin/effective_customers_datatable.rb +22 -0
  31. data/app/datatables/admin/effective_orders_datatable.rb +100 -0
  32. data/app/datatables/effective_orders_datatable.rb +79 -0
  33. data/app/helpers/effective_carts_helper.rb +113 -0
  34. data/app/helpers/effective_orders_helper.rb +143 -0
  35. data/app/helpers/effective_paypal_helper.rb +49 -0
  36. data/app/helpers/effective_stripe_helper.rb +85 -0
  37. data/app/helpers/effective_subscriptions_helper.rb +34 -0
  38. data/app/mailers/effective/orders_mailer.rb +196 -0
  39. data/app/models/concerns/acts_as_purchasable.rb +118 -0
  40. data/app/models/concerns/acts_as_subscribable.rb +90 -0
  41. data/app/models/concerns/acts_as_subscribable_buyer.rb +49 -0
  42. data/app/models/effective/access_denied.rb +17 -0
  43. data/app/models/effective/cart.rb +88 -0
  44. data/app/models/effective/cart_item.rb +40 -0
  45. data/app/models/effective/customer.rb +92 -0
  46. data/app/models/effective/order.rb +541 -0
  47. data/app/models/effective/order_item.rb +63 -0
  48. data/app/models/effective/product.rb +23 -0
  49. data/app/models/effective/sold_out_validator.rb +7 -0
  50. data/app/models/effective/subscripter.rb +185 -0
  51. data/app/models/effective/subscription.rb +95 -0
  52. data/app/models/effective/tax_rate_calculator.rb +48 -0
  53. data/app/views/admin/customers/_actions.html.haml +2 -0
  54. data/app/views/admin/customers/index.html.haml +6 -0
  55. data/app/views/admin/customers/show.html.haml +6 -0
  56. data/app/views/admin/order_items/index.html.haml +3 -0
  57. data/app/views/admin/orders/_datatable_actions.html.haml +18 -0
  58. data/app/views/admin/orders/_form.html.haml +35 -0
  59. data/app/views/admin/orders/_form_note_internal.html.haml +7 -0
  60. data/app/views/admin/orders/_order_actions.html.haml +9 -0
  61. data/app/views/admin/orders/_order_item_fields.html.haml +14 -0
  62. data/app/views/admin/orders/checkout.html.haml +3 -0
  63. data/app/views/admin/orders/edit.html.haml +6 -0
  64. data/app/views/admin/orders/index.html.haml +6 -0
  65. data/app/views/admin/orders/new.html.haml +4 -0
  66. data/app/views/admin/orders/show.html.haml +4 -0
  67. data/app/views/effective/carts/_cart.html.haml +28 -0
  68. data/app/views/effective/carts/_cart_actions.html.haml +3 -0
  69. data/app/views/effective/carts/show.html.haml +17 -0
  70. data/app/views/effective/customers/_customer.html.haml +72 -0
  71. data/app/views/effective/customers/_form.html.haml +21 -0
  72. data/app/views/effective/customers/edit.html.haml +4 -0
  73. data/app/views/effective/customers/update.js.erb +5 -0
  74. data/app/views/effective/orders/_checkout_actions.html.haml +3 -0
  75. data/app/views/effective/orders/_checkout_step1.html.haml +4 -0
  76. data/app/views/effective/orders/_checkout_step2.html.haml +37 -0
  77. data/app/views/effective/orders/_datatable_actions.html.haml +2 -0
  78. data/app/views/effective/orders/_fields.html.haml +31 -0
  79. data/app/views/effective/orders/_fields_note.html.haml +7 -0
  80. data/app/views/effective/orders/_fields_terms.html.haml +8 -0
  81. data/app/views/effective/orders/_order.html.haml +11 -0
  82. data/app/views/effective/orders/_order_actions.html.haml +18 -0
  83. data/app/views/effective/orders/_order_deferred.html.haml +9 -0
  84. data/app/views/effective/orders/_order_footer.html.haml +1 -0
  85. data/app/views/effective/orders/_order_header.html.haml +23 -0
  86. data/app/views/effective/orders/_order_items.html.haml +72 -0
  87. data/app/views/effective/orders/_order_notes.html.haml +17 -0
  88. data/app/views/effective/orders/_order_payment.html.haml +24 -0
  89. data/app/views/effective/orders/_order_shipping.html.haml +30 -0
  90. data/app/views/effective/orders/_orders_table.html.haml +23 -0
  91. data/app/views/effective/orders/cheque/_form.html.haml +4 -0
  92. data/app/views/effective/orders/declined.html.haml +12 -0
  93. data/app/views/effective/orders/deferred.html.haml +13 -0
  94. data/app/views/effective/orders/deferred/_form.html.haml +16 -0
  95. data/app/views/effective/orders/edit.html.haml +3 -0
  96. data/app/views/effective/orders/free/_form.html.haml +5 -0
  97. data/app/views/effective/orders/index.html.haml +3 -0
  98. data/app/views/effective/orders/mark_as_paid/_form.html.haml +23 -0
  99. data/app/views/effective/orders/moneris/_form.html.haml +47 -0
  100. data/app/views/effective/orders/new.html.haml +3 -0
  101. data/app/views/effective/orders/paypal/_form.html.haml +5 -0
  102. data/app/views/effective/orders/phone/_form.html.haml +4 -0
  103. data/app/views/effective/orders/pretend/_form.html.haml +8 -0
  104. data/app/views/effective/orders/purchased.html.haml +11 -0
  105. data/app/views/effective/orders/refund/_form.html.haml +5 -0
  106. data/app/views/effective/orders/show.html.haml +6 -0
  107. data/app/views/effective/orders/stripe/_element.html.haml +8 -0
  108. data/app/views/effective/orders/stripe/_form.html.haml +31 -0
  109. data/app/views/effective/orders_mailer/order_error.html.haml +11 -0
  110. data/app/views/effective/orders_mailer/order_receipt_to_admin.html.haml +2 -0
  111. data/app/views/effective/orders_mailer/order_receipt_to_buyer.html.haml +2 -0
  112. data/app/views/effective/orders_mailer/payment_request_to_buyer.html.haml +13 -0
  113. data/app/views/effective/orders_mailer/pending_order_invoice_to_buyer.html.haml +13 -0
  114. data/app/views/effective/orders_mailer/refund_notification_to_admin.html.haml +15 -0
  115. data/app/views/effective/orders_mailer/subscription_canceled.html.haml +9 -0
  116. data/app/views/effective/orders_mailer/subscription_created.html.haml +13 -0
  117. data/app/views/effective/orders_mailer/subscription_event_to_admin.html.haml +13 -0
  118. data/app/views/effective/orders_mailer/subscription_payment_failed.html.haml +9 -0
  119. data/app/views/effective/orders_mailer/subscription_payment_succeeded.html.haml +9 -0
  120. data/app/views/effective/orders_mailer/subscription_trial_expired.html.haml +5 -0
  121. data/app/views/effective/orders_mailer/subscription_trialing.html.haml +7 -0
  122. data/app/views/effective/orders_mailer/subscription_updated.html.haml +13 -0
  123. data/app/views/effective/subscripter/_form.html.haml +60 -0
  124. data/app/views/effective/subscripter/_plan.html.haml +23 -0
  125. data/app/views/layouts/effective_orders_mailer_layout.html.haml +25 -0
  126. data/config/effective_orders.rb +279 -0
  127. data/config/routes.rb +70 -0
  128. data/db/migrate/01_create_effective_orders.rb.erb +137 -0
  129. data/lib/effective_orders.rb +243 -0
  130. data/lib/effective_orders/engine.rb +60 -0
  131. data/lib/effective_orders/version.rb +3 -0
  132. data/lib/generators/effective_orders/install_generator.rb +63 -0
  133. data/lib/generators/templates/effective_orders_mailer_preview.rb +120 -0
  134. data/lib/tasks/effective_orders_tasks.rake +69 -0
  135. metadata +276 -0
@@ -0,0 +1,541 @@
1
+ # When an Order is first initialized it is done in the pending state
2
+ # - when it's in the pending state, none of the buyer entered information is required
3
+ # - when a pending order is rendered:
4
+ # - if the user has a billing address, go to step 2
5
+ # - if the user has no billing address, go to step 1
6
+ #
7
+ # After Step1, we go to the confirmed state
8
+ # After Step2, we are in the purchased or declined state
9
+
10
+ module Effective
11
+ class Order < ActiveRecord::Base
12
+ self.table_name = EffectiveOrders.orders_table_name.to_s
13
+
14
+ if EffectiveOrders.obfuscate_order_ids
15
+ acts_as_obfuscated format: '###-####-###'
16
+ end
17
+
18
+ acts_as_addressable(
19
+ billing: { singular: true, use_full_name: EffectiveOrders.use_address_full_name },
20
+ shipping: { singular: true, use_full_name: EffectiveOrders.use_address_full_name }
21
+ )
22
+
23
+ attr_accessor :terms_and_conditions # Yes, I agree to the terms and conditions
24
+ attr_accessor :confirmed_checkout # Set on the Checkout Step 1
25
+
26
+ # Settings in the /admin action forms
27
+ attr_accessor :send_payment_request_to_buyer # Set by Admin::Orders#new. Should the payment request email be sent after creating an order?
28
+ attr_accessor :send_mark_as_paid_email_to_buyer # Set by Admin::Orders#mark_as_paid
29
+ attr_accessor :skip_buyer_validations # Set by Admin::Orders#create
30
+
31
+ # If we want to use orders in a has_many way
32
+ belongs_to :parent, polymorphic: true, optional: true
33
+
34
+ belongs_to :user, validate: false # This is the buyer/user of the order. We validate it below.
35
+ has_many :order_items, -> { order(:id) }, inverse_of: :order, class_name: 'Effective::OrderItem', dependent: :delete_all
36
+
37
+ accepts_nested_attributes_for :order_items, allow_destroy: false, reject_if: :all_blank
38
+ accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
39
+
40
+ # Attributes
41
+ # state :string
42
+ # purchased_at :datetime
43
+ #
44
+ # note :text # From buyer to admin
45
+ # note_to_buyer :text # From admin to buyer
46
+ # note_internal :text # Internal admin only
47
+ #
48
+ # billing_name :string # name of buyer
49
+ # email :string # same as user.email
50
+ # cc :string # can be set by admin
51
+ #
52
+ # payment :text # serialized hash containing all the payment details.
53
+ # payment_provider :string
54
+ # payment_card :string
55
+ #
56
+ # tax_rate :decimal, precision: 6, scale: 3
57
+ #
58
+ # subtotal :integer
59
+ # tax :integer
60
+ # total :integer
61
+ #
62
+ # timestamps
63
+
64
+ serialize :payment, Hash
65
+
66
+ before_validation { assign_order_totals }
67
+ before_validation { assign_billing_name }
68
+ before_validation { assign_email }
69
+ before_validation { assign_last_address }
70
+
71
+ before_validation(if: -> { confirmed_checkout }) do
72
+ self.state = EffectiveOrders::CONFIRMED if pending?
73
+ end
74
+
75
+ # Order validations
76
+ validates :user_id, presence: true
77
+ validates :email, presence: true, email: true # email and cc validators are from effective_resources
78
+ validates :cc, email_cc: true
79
+
80
+ validates :order_items, presence: { message: 'No items are present. Please add additional items.' }
81
+ validates :state, inclusion: { in: EffectiveOrders::STATES.keys }
82
+ validates :subtotal, presence: true
83
+
84
+ if EffectiveOrders.minimum_charge.to_i > 0
85
+ validates :total, presence: true, numericality: {
86
+ greater_than_or_equal_to: EffectiveOrders.minimum_charge.to_i,
87
+ message: "must be $#{'%0.2f' % (EffectiveOrders.minimum_charge.to_i / 100.0)} or more. Please add additional items."
88
+ }, unless: -> { (free? && EffectiveOrders.free?) || (refund? && EffectiveOrders.refund?) }
89
+ end
90
+
91
+ validate(if: -> { tax_rate.present? }) do
92
+ if (tax_rate > 100.0 || (tax_rate < 0.25 && tax_rate > 0.0000))
93
+ errors.add(:tax_rate, "is invalid. expected a value between 100.0 (100%) and 0.25 (0.25%) or 0")
94
+ end
95
+ end
96
+
97
+ # User validations -- An admin skips these when working in the admin/ namespace
98
+ with_options unless: -> { pending? || skip_buyer_validations? } do
99
+ validates :tax_rate, presence: { message: "can't be determined based on billing address" }
100
+ validates :tax, presence: true
101
+
102
+ if EffectiveOrders.billing_address
103
+ validates :billing_address, presence: true
104
+ end
105
+
106
+ if EffectiveOrders.shipping_address
107
+ validates :shipping_address, presence: true
108
+ end
109
+
110
+ if EffectiveOrders.collect_note_required
111
+ validates :note, presence: true
112
+ end
113
+ end
114
+
115
+ with_options if: -> { confirmed? && !skip_buyer_validations? } do
116
+ if EffectiveOrders.terms_and_conditions
117
+ validates :terms_and_conditions, presence: true
118
+ end
119
+ end
120
+
121
+ # When Purchased
122
+ with_options if: -> { purchased? } do
123
+ validates :purchased_at, presence: true
124
+ validates :payment, presence: true
125
+
126
+ validates :payment_provider, presence: true, inclusion: { in: EffectiveOrders.payment_providers }
127
+ validates :payment_card, presence: true
128
+ end
129
+
130
+ with_options if: -> { deferred? } do
131
+ validates :payment_provider, presence: true, inclusion: { in: EffectiveOrders.deferred_providers }
132
+ end
133
+
134
+ scope :deep, -> { includes(:user, order_items: :purchasable) }
135
+ scope :sorted, -> { order(:id) }
136
+
137
+ scope :purchased, -> { where(state: EffectiveOrders::PURCHASED) }
138
+ scope :purchased_by, lambda { |user| purchased.where(user: user) }
139
+ scope :not_purchased, -> { where.not(state: EffectiveOrders::PURCHASED) }
140
+
141
+ scope :pending, -> { where(state: EffectiveOrders::PENDING) }
142
+ scope :confirmed, -> { where(state: EffectiveOrders::CONFIRMED) }
143
+ scope :deferred, -> { where(state: EffectiveOrders::DEFERRED) }
144
+ scope :declined, -> { where(state: EffectiveOrders::DECLINED) }
145
+ scope :refunds, -> { purchased.where('total < ?', 0) }
146
+
147
+ # Effective::Order.new()
148
+ # Effective::Order.new(Product.first)
149
+ # Effective::Order.new(current_cart)
150
+ # Effective::Order.new(Effective::Order.last)
151
+
152
+ # Effective::Order.new(items: Product.first)
153
+ # Effective::Order.new(items: [Product.first, Product.second], user: User.first)
154
+ # Effective::Order.new(items: Product.first, user: User.first, billing_address: Effective::Address.new, shipping_address: Effective::Address.new)
155
+
156
+ def initialize(atts = nil, &block)
157
+ super(state: EffectiveOrders::PENDING) # Initialize with state: PENDING
158
+
159
+ return unless atts.present?
160
+
161
+ if atts.kind_of?(Hash)
162
+ items = Array(atts.delete(:item)) + Array(atts.delete(:items))
163
+
164
+ self.user = atts.delete(:user) || (items.first.user if items.first.respond_to?(:user))
165
+
166
+ if (address = atts.delete(:billing_address)).present?
167
+ self.billing_address = address
168
+ self.billing_address.full_name ||= user.to_s.presence
169
+ end
170
+
171
+ if (address = atts.delete(:shipping_address)).present?
172
+ self.shipping_address = address
173
+ self.shipping_address.full_name ||= user.to_s.presence
174
+ end
175
+
176
+ atts.each { |key, value| self.send("#{key}=", value) }
177
+
178
+ add(items) if items.present?
179
+ else # Attributes are not a Hash
180
+ self.user = atts.user if atts.respond_to?(:user)
181
+ add(atts)
182
+ end
183
+ end
184
+
185
+ # Items can be an Effective::Cart, an Effective::order, a single acts_as_purchasable, or multiple acts_as_purchasables
186
+ # add(Product.first) => returns an Effective::OrderItem
187
+ # add(Product.first, current_cart) => returns an array of Effective::OrderItems
188
+ def add(*items, quantity: 1)
189
+ raise 'unable to alter a purchased order' if purchased?
190
+ raise 'unable to alter a declined order' if declined?
191
+
192
+ cart_items = items.flatten.flat_map do |item|
193
+ if item.kind_of?(Effective::Cart)
194
+ item.cart_items.to_a
195
+ elsif item.kind_of?(ActsAsPurchasable)
196
+ Effective::CartItem.new(quantity: quantity, purchasable: item)
197
+ elsif item.kind_of?(Effective::Order)
198
+ # Duplicate an existing order
199
+ self.note_to_buyer ||= item.note_to_buyer
200
+ self.note_internal ||= item.note_internal
201
+
202
+ item.order_items.select { |oi| oi.purchasable.kind_of?(Effective::Product) }.map do |oi|
203
+ product = Effective::Product.new(name: oi.purchasable.purchasable_name, price: oi.purchasable.price, tax_exempt: oi.purchasable.tax_exempt)
204
+ Effective::CartItem.new(quantity: oi.quantity, purchasable: product)
205
+ end
206
+ else
207
+ raise 'add() expects one or more acts_as_purchasable objects, or an Effective::Cart'
208
+ end
209
+ end.compact
210
+
211
+ # Make sure to reset stored aggregates
212
+ self.total = nil
213
+ self.subtotal = nil
214
+ self.tax = nil
215
+
216
+ retval = cart_items.map do |item|
217
+ order_items.build(
218
+ name: item.name,
219
+ quantity: item.quantity,
220
+ price: item.price,
221
+ tax_exempt: (item.tax_exempt || false),
222
+ ).tap { |order_item| order_item.purchasable = item.purchasable }
223
+ end
224
+
225
+ retval.size == 1 ? retval.first : retval
226
+ end
227
+
228
+ def to_s
229
+ if refund?
230
+ "Refund ##{to_param}"
231
+ elsif purchased?
232
+ "Receipt ##{to_param}"
233
+ elsif pending?
234
+ "Pending Order ##{to_param}"
235
+ else
236
+ "Order ##{to_param}"
237
+ end
238
+ end
239
+
240
+ def pending?
241
+ state == EffectiveOrders::PENDING
242
+ end
243
+
244
+ def confirmed?
245
+ state == EffectiveOrders::CONFIRMED
246
+ end
247
+
248
+ def deferred?
249
+ state == EffectiveOrders::DEFERRED
250
+ end
251
+
252
+ def purchased?(provider = nil)
253
+ return false if (state != EffectiveOrders::PURCHASED)
254
+ return true if provider.nil? || payment_provider == provider.to_s
255
+ false
256
+ end
257
+
258
+ def declined?
259
+ state == EffectiveOrders::DECLINED
260
+ end
261
+
262
+ def purchasables
263
+ order_items.map { |order_item| order_item.purchasable }
264
+ end
265
+
266
+ def subtotal
267
+ self[:subtotal] || order_items.map { |oi| oi.subtotal }.sum
268
+ end
269
+
270
+ def tax_rate
271
+ self[:tax_rate] || get_tax_rate()
272
+ end
273
+
274
+ def tax
275
+ self[:tax] || get_tax()
276
+ end
277
+
278
+ def total
279
+ (self[:total] || (subtotal + tax.to_i)).to_i
280
+ end
281
+
282
+ def free?
283
+ total == 0
284
+ end
285
+
286
+ def refund?
287
+ total.to_i < 0
288
+ end
289
+
290
+ def num_items
291
+ order_items.map { |oi| oi.quantity }.sum
292
+ end
293
+
294
+ def send_payment_request_to_buyer?
295
+ truthy?(send_payment_request_to_buyer) && !free? && !refund?
296
+ end
297
+
298
+ def send_mark_as_paid_email_to_buyer?
299
+ truthy?(send_mark_as_paid_email_to_buyer)
300
+ end
301
+
302
+ def skip_buyer_validations?
303
+ truthy?(skip_buyer_validations)
304
+ end
305
+
306
+ # This is called from admin/orders#create
307
+ # This is intended for use as an admin action only
308
+ # It skips any address or bad user validations
309
+ # It's basically the same as save! on a new order, except it might send the payment request to buyer
310
+ def pending!
311
+ self.state = EffectiveOrders::PENDING
312
+ self.addresses.clear if addresses.any? { |address| address.valid? == false }
313
+ save!
314
+
315
+ send_payment_request_to_buyer! if send_payment_request_to_buyer?
316
+ true
317
+ end
318
+
319
+ # Used by admin checkout only
320
+ def confirm!
321
+ update!(state: EffectiveOrders::CONFIRMED)
322
+ end
323
+
324
+ # This lets us skip to the confirmed workflow for an admin...
325
+ def assign_confirmed_if_valid!
326
+ return unless pending?
327
+
328
+ self.state = EffectiveOrders::CONFIRMED
329
+ return true if valid?
330
+
331
+ self.errors.clear
332
+ self.state = EffectiveOrders::PENDING
333
+ false
334
+ end
335
+
336
+ # Effective::Order.new(items: Product.first, user: User.first).purchase!(email: false)
337
+ def purchase!(payment: 'none', provider: 'none', card: 'none', email: true, skip_buyer_validations: false)
338
+ return false if purchased?
339
+ error = nil
340
+
341
+ assign_attributes(
342
+ state: EffectiveOrders::PURCHASED,
343
+ payment: payment_to_h(payment),
344
+ payment_provider: provider,
345
+ payment_card: (card.presence || 'none'),
346
+ skip_buyer_validations: skip_buyer_validations
347
+ )
348
+
349
+ self.purchased_at ||= Time.zone.now
350
+
351
+ Effective::Order.transaction do
352
+ begin
353
+ run_purchasable_callbacks(:before_purchase)
354
+ save!
355
+ update_purchasables_purchased_order!
356
+ rescue => e
357
+ self.state = state_was
358
+ self.purchased_at = nil
359
+
360
+ error = e.message
361
+ raise ::ActiveRecord::Rollback
362
+ end
363
+ end
364
+
365
+ raise "Failed to purchase order: #{error || errors.full_messages.to_sentence}" unless error.nil?
366
+
367
+ send_refund_notification! if email && refund?
368
+ send_order_receipts! if email
369
+
370
+ run_purchasable_callbacks(:after_purchase)
371
+
372
+ true
373
+ end
374
+
375
+ def defer!(provider: 'none', email: true)
376
+ return false if purchased?
377
+
378
+ assign_attributes(
379
+ state: EffectiveOrders::DEFERRED,
380
+ payment_provider: provider
381
+ )
382
+
383
+ save!
384
+
385
+ send_payment_request_to_buyer! if email
386
+
387
+ true
388
+ end
389
+
390
+ def decline!(payment: 'none', provider: 'none', card: 'none', validate: true)
391
+ return false if declined?
392
+
393
+ raise EffectiveOrders::AlreadyPurchasedException.new('order already purchased') if purchased?
394
+
395
+ error = nil
396
+
397
+ assign_attributes(
398
+ state: EffectiveOrders::DECLINED,
399
+ purchased_at: nil,
400
+ payment: payment_to_h(payment),
401
+ payment_provider: provider,
402
+ payment_card: (card.presence || 'none'),
403
+ skip_buyer_validations: true
404
+ )
405
+
406
+ Effective::Order.transaction do
407
+ begin
408
+ save!(validate: validate)
409
+ rescue => e
410
+ self.state = state_was
411
+
412
+ error = e.message
413
+ raise ::ActiveRecord::Rollback
414
+ end
415
+ end
416
+
417
+ raise "Failed to decline order: #{error || errors.full_messages.to_sentence}" unless error.nil?
418
+
419
+ run_purchasable_callbacks(:after_decline)
420
+
421
+ true
422
+ end
423
+
424
+ # Doesn't control anything. Purely for the flash messaging
425
+ def emails_send_to
426
+ [email, cc.presence].compact.to_sentence
427
+ end
428
+
429
+ def send_order_receipts!
430
+ send_order_receipt_to_admin! if EffectiveOrders.mailer[:send_order_receipt_to_admin]
431
+ send_order_receipt_to_buyer! if EffectiveOrders.mailer[:send_order_receipt_to_buyer]
432
+ end
433
+
434
+ def send_order_receipt_to_admin!
435
+ send_email(:order_receipt_to_admin, to_param) if purchased?
436
+ end
437
+
438
+ def send_order_receipt_to_buyer!
439
+ send_email(:order_receipt_to_buyer, to_param) if purchased?
440
+ end
441
+
442
+ def send_payment_request_to_buyer!
443
+ send_email(:payment_request_to_buyer, to_param) unless purchased?
444
+ end
445
+
446
+ def send_pending_order_invoice_to_buyer!
447
+ send_email(:pending_order_invoice_to_buyer, to_param) unless purchased?
448
+ end
449
+
450
+ def send_refund_notification!
451
+ send_email(:refund_notification_to_admin, to_param) if purchased? && refund?
452
+ end
453
+
454
+ def skip_qb_sync!
455
+ defined?(EffectiveQbSync) ? EffectiveQbSync.skip_order!(self) : true
456
+ end
457
+
458
+ protected
459
+
460
+ def get_tax_rate
461
+ rate = instance_exec(self, &EffectiveOrders.order_tax_rate_method).to_f
462
+
463
+ if (rate > 100.0 || (rate < 0.25 && rate > 0.0000))
464
+ raise "expected EffectiveOrders.order_tax_rate_method to return a value between 100.0 (100%) and 0.25 (0.25%) or 0 or nil. Received #{rate}. Please return 5.25 for 5.25% tax."
465
+ end
466
+
467
+ rate
468
+ end
469
+
470
+ def get_tax
471
+ return nil unless tax_rate.present?
472
+ order_items.reject { |oi| oi.tax_exempt? }.map { |oi| (oi.subtotal * (tax_rate / 100.0)).round(0).to_i }.sum
473
+ end
474
+
475
+ private
476
+
477
+ def assign_order_totals
478
+ self.subtotal = order_items.map { |oi| oi.subtotal }.sum
479
+ self.tax_rate = get_tax_rate() unless (tax_rate || 0) > 0
480
+ self.tax = get_tax()
481
+ self.total = subtotal + (tax || 0)
482
+ end
483
+
484
+ def assign_billing_name
485
+ self.billing_name = [(billing_address.full_name.presence if billing_address.present?), (user.to_s.presence)].compact.first
486
+ end
487
+
488
+ def assign_email
489
+ self.email = user&.email
490
+ end
491
+
492
+ def assign_last_address
493
+ return unless user.present?
494
+ return unless (EffectiveOrders.billing_address || EffectiveOrders.shipping_address)
495
+ return if EffectiveOrders.billing_address && billing_address.present?
496
+ return if EffectiveOrders.shipping_address && shipping_address.present?
497
+
498
+ last_order = Effective::Order.sorted.where(user: user).last
499
+ return unless last_order.present?
500
+
501
+ if EffectiveOrders.billing_address && last_order.billing_address.present?
502
+ self.billing_address = last_order.billing_address
503
+ end
504
+
505
+ if EffectiveOrders.shipping_address && last_order.shipping_address.present?
506
+ self.shipping_address = last_order.shipping_address
507
+ end
508
+ end
509
+
510
+ def update_purchasables_purchased_order!
511
+ order_items.each { |oi| oi.purchasable&.update_column(:purchased_order_id, self.id) }
512
+ end
513
+
514
+ def run_purchasable_callbacks(name)
515
+ order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
516
+ end
517
+
518
+ def send_email(email, *mailer_args)
519
+ Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
520
+ end
521
+
522
+ def truthy?(value)
523
+ if defined?(::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES) # Rails <5
524
+ ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(value)
525
+ else
526
+ ::ActiveRecord::Type::Boolean.new.cast(value)
527
+ end
528
+ end
529
+
530
+ def payment_to_h(payment)
531
+ if payment.respond_to?(:to_unsafe_h)
532
+ payment.to_unsafe_h.to_h
533
+ elsif payment.respond_to?(:to_h)
534
+ payment.to_h
535
+ else
536
+ { details: (payment.to_s.presence || 'none') }
537
+ end
538
+ end
539
+
540
+ end
541
+ end