effective_orders 5.9.3 → 6.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c21ba05da7239366ba59d58f08bc997193192c6b9cb83c93caade90b61072bd
4
- data.tar.gz: 965fe919c2c7a6537dd7b15427291cef1119c768ccd38cf1fdbcaf282b1fd95a
3
+ metadata.gz: f548373346f66befb20e325b166d275eff0444b9b8d2865f87c37d8116d32b0d
4
+ data.tar.gz: c2cdbd728306010ba79e3847fd59578e3b51afbf2764021b3581767bad0d6831
5
5
  SHA512:
6
- metadata.gz: 344e2cdaa0de66f512335a2c9da024de369713f34c84480a1b813b59874343bc4fc2ac81579f35ce718153346eb9e53826f46e657a5def99a6ff266b0f0aa45e
7
- data.tar.gz: 51f49f85b475d26fea9cfc593f627ed4e1c1761075d6fb508264419246485035995272e638da63cb5ed99635a9d7ff4b1aad0c1c34b8bc7c385e26eda737f069
6
+ metadata.gz: 027327c45635652ad18bf89f0597d3c22156adf9ba04a527014c5aae2daed01a92f50dd6d134cf7afc5a34b6ca02daae7b69e73fd801269bf22f75e07f61cc16
7
+ data.tar.gz: 54576594dab1cd9a569e7c37aab4cf945b8670628d92868a85a8d068a212a8f175cf31c0955fbb933fb618efde82cd9a0d090cb8a83446c876ea74b0a9b10c0a
@@ -1,31 +1,37 @@
1
1
  .effective-order {
2
2
  table {
3
- clear: both;
4
- margin-bottom: 25px;
3
+ .quantity { width: 0%; }
5
4
 
6
- tfoot {
7
- > tr:first-child {
8
- border-top: 20px solid transparent;
9
- }
5
+ .price {
6
+ text-align: right;
7
+ padding-left: 2rem;
8
+ }
10
9
 
11
- th {
10
+ tfoot {
11
+ th, td {
12
12
  border: none;
13
13
  text-align: right;
14
- padding-right: 65px;
14
+ white-space: nowrap;
15
15
  }
16
16
 
17
- td {
18
- text-align: right;
17
+ // This is the first column
18
+ th { width: 100%; }
19
+
20
+ tr.single-line {
21
+ td { border-top: solid 1px; }
22
+ }
23
+
24
+ tr.double-line {
25
+ td { border-top: double 4px; }
19
26
  }
20
27
 
21
- .actions { border: none; }
28
+ .amount-owing { font-style: italic; }
29
+ .total { font-style: italic; }
22
30
  }
23
31
  }
24
-
25
- .price { text-align: right; }
26
32
  }
27
33
 
28
- // Print - Resend Receipt. Ontop of order.
34
+ // Print - Resend Receipt. Ontop of order.
29
35
  .effective-order-actions {
30
36
  text-align: right;
31
37
  margin-bottom: 0.5rem;
@@ -13,7 +13,7 @@ module Effective
13
13
 
14
14
  payment = validate_stripe_payment(stripe_params[:payment_intent_id])
15
15
 
16
- if payment.blank? || !payment.kind_of?(Hash)
16
+ if payment.blank?
17
17
  return order_declined(payment: payment, provider: 'stripe', declined_url: stripe_params[:declined_url])
18
18
  end
19
19
 
@@ -37,36 +37,35 @@ module Effective
37
37
  end
38
38
 
39
39
  def validate_stripe_payment(payment_intent_id)
40
- begin
41
- intent = EffectiveOrders.with_stripe { ::Stripe::PaymentIntent.retrieve(payment_intent_id) }
42
-
43
- raise('status is not succeeded') unless intent.status == 'succeeded'
44
- raise('charges are not present') unless intent.charges.present?
45
-
46
- charge = intent.charges.data.first
47
- raise('charge not succeeded') unless charge.status == 'succeeded'
48
-
49
- card = charge.payment_method_details.try(:card) || {}
50
- active_card = "**** **** **** #{card['last4']} #{card['brand']} #{card['exp_month']}/#{card['exp_year']}" if card.present?
51
-
52
- {
53
- charge_id: charge.id,
54
- payment_method_id: charge.payment_method,
55
- payment_intent_id: intent.id,
56
-
57
- active_card: active_card,
58
- card: card['brand'],
59
-
60
- amount: charge.amount,
61
- created: charge.created,
62
- currency: charge.currency,
63
- customer: charge.customer,
64
- status: charge.status
65
- }.compact
66
-
67
- rescue => e
68
- e.message
69
- end
40
+ intent = EffectiveOrders.with_stripe { ::Stripe::PaymentIntent.retrieve(payment_intent_id) }
41
+ raise('expected stripe intent to be present') if intent.blank?
42
+ return unless intent.status == 'succeeded'
43
+
44
+ # Stripe API version 2022-11-15 and 2022-08-01
45
+ charge_id = intent.try(:latest_charge) || (intent.charges.data.first.id rescue nil)
46
+ raise('expected stripe charge_id to be present') if charge_id.blank?
47
+
48
+ charge = EffectiveOrders.with_stripe { ::Stripe::Charge.retrieve(charge_id) }
49
+ raise('expected stripe charge to be present') if charge.blank?
50
+ return unless charge.status == 'succeeded'
51
+
52
+ card = charge.payment_method_details.try(:card) || {}
53
+ active_card = "**** **** **** #{card['last4']} #{card['brand']} #{card['exp_month']}/#{card['exp_year']}" if card.present?
54
+
55
+ {
56
+ charge_id: charge.id,
57
+ payment_method_id: charge.payment_method,
58
+ payment_intent_id: intent.id,
59
+
60
+ active_card: active_card,
61
+ card: card['brand'],
62
+
63
+ amount: charge.amount,
64
+ created: charge.created,
65
+ currency: charge.currency,
66
+ customer: charge.customer,
67
+ status: charge.status
68
+ }.compact
70
69
  end
71
70
 
72
71
  end
@@ -28,7 +28,7 @@ class Admin::EffectiveOrdersDatatable < Effective::Datatable
28
28
  end
29
29
 
30
30
  datatable do
31
- order :id, :desc
31
+ order :updated_at
32
32
 
33
33
  bulk_actions_col
34
34
 
@@ -66,20 +66,19 @@ class Admin::EffectiveOrdersDatatable < Effective::Datatable
66
66
  end
67
67
 
68
68
  col :payment_method
69
- col :payment_provider
69
+ col :payment_provider, label: 'Provider', visible: false, search: { collection: EffectiveOrders.admin_payment_providers }
70
+ col :payment_card, label: 'Card', visible: false
70
71
 
71
72
  col :subtotal, as: :price, visible: false
73
+
72
74
  col :tax, as: :price, visible: false
75
+ col(:tax_rate, visible: false) { |order| rate_to_percentage(order.tax_rate) }
73
76
 
74
- col :tax_rate, visible: false do |order|
75
- tax_rate_to_percentage(order.tax_rate)
76
- end
77
+ col :surcharge, as: :price, visible: false
78
+ col(:surcharge_percent, visible: false) { |order| rate_to_percentage(order.surcharge_percent) }
77
79
 
78
80
  col :total, as: :price
79
81
 
80
- col :payment_provider, label: 'Provider', visible: false, search: { collection: EffectiveOrders.admin_payment_providers }
81
- col :payment_card, label: 'Card', visible: false
82
-
83
82
  if EffectiveOrders.collect_note
84
83
  col :note, visible: false
85
84
  end
@@ -42,18 +42,20 @@ class EffectiveOrdersDatatable < Effective::Datatable
42
42
  collection.where(id: Effective::OrderItem.where('name ILIKE ?', "%#{term}%").select('order_id'))
43
43
  end
44
44
 
45
+ col :payment_method, visible: false
46
+ col :payment_provider, label: 'Provider', visible: false, search: { collection: EffectiveOrders.payment_providers }
47
+ col :payment_card, label: 'Card', visible: false
48
+
45
49
  col :subtotal, as: :price, visible: false
50
+
46
51
  col :tax, as: :price, visible: false
52
+ col(:tax_rate, visible: false) { |order| rate_to_percentage(order.tax_rate) }
47
53
 
48
- col :tax_rate, visible: false do |order|
49
- tax_rate_to_percentage(order.tax_rate)
50
- end
54
+ col :surcharge, as: :price, visible: false
55
+ col(:surcharge_percent, visible: false) { |order| rate_to_percentage(order.surcharge_percent) }
51
56
 
52
57
  col :total, as: :price
53
58
 
54
- col :payment_provider, label: 'Provider', visible: false, search: { collection: EffectiveOrders.payment_providers }
55
- col :payment_card, label: 'Card', visible: false
56
-
57
59
  if EffectiveOrders.collect_note
58
60
  col :note
59
61
  end
@@ -103,11 +103,12 @@ module EffectiveCartsHelper
103
103
 
104
104
  def render_cart(cart = nil)
105
105
  cart ||= current_cart
106
- render(partial: 'effective/carts/cart', locals: { cart: cart })
106
+ render('effective/carts/cart', cart: cart)
107
107
  end
108
108
 
109
109
  def render_purchasables(*purchasables)
110
- render(partial: 'effective/orders/order_items', locals: { order: Effective::Order.new(purchasables) })
110
+ order = Effective::Order.new(purchasables)
111
+ render('effective/orders/purchasables', order: order)
111
112
  end
112
113
 
113
114
  end
@@ -10,9 +10,8 @@ module EffectiveMonerisCheckoutHelper
10
10
  api_token: EffectiveOrders.moneris_checkout.fetch(:api_token),
11
11
  store_id: EffectiveOrders.moneris_checkout.fetch(:store_id),
12
12
  checkout_id: EffectiveOrders.moneris_checkout.fetch(:checkout_id),
13
-
14
13
  action: :preload,
15
- txn_total: price_to_currency(order.total).gsub(',', '').gsub('$', ''),
14
+ txn_total: ('%.2f' % (order.total_with_surcharge / 100.0)),
16
15
 
17
16
  # Optional
18
17
  order_no: order.transaction_id, # Has to be unique. This is order number, billing name and Time.now
@@ -5,6 +5,12 @@ module EffectiveOrdersHelper
5
5
  number_to_currency(price / 100.0)
6
6
  end
7
7
 
8
+ def rate_to_percentage(rate)
9
+ rate = rate || 0.0
10
+ number_to_percentage(rate, strip_insignificant_zeros: true)
11
+ end
12
+
13
+ # Deprecated.
8
14
  def tax_rate_to_percentage(tax_rate, options = {})
9
15
  options[:strip_insignificant_zeros] = true if options[:strip_insignificant_zeros].nil?
10
16
  number_to_percentage(tax_rate, strip_insignificant_zeros: true)
@@ -119,4 +125,21 @@ module EffectiveOrdersHelper
119
125
  icon_to('shopping-cart', path, { title: 'Checkout' }.merge(options))
120
126
  end
121
127
 
128
+ def admin_mark_as_paid_payment_providers
129
+ providers = EffectiveOrders.admin_payment_providers
130
+
131
+ percentage = EffectiveOrders.credit_card_surcharge_percent.to_f
132
+ return providers unless percentage > 0.0
133
+
134
+ surcharge_providers = EffectiveOrders.credit_card_payment_providers
135
+
136
+ with_surcharge = providers.select { |provider| surcharge_providers.include?(provider) }
137
+ without_surcharge = providers.reject { |provider| surcharge_providers.include?(provider) }
138
+
139
+ {
140
+ "With #{rate_to_percentage(percentage)} credit card surcharge": with_surcharge.map { |provider| [provider, provider] },
141
+ 'Without credit card surcharge': without_surcharge.map { |provider| [provider, provider] }
142
+ }
143
+ end
144
+
122
145
  end
@@ -31,16 +31,31 @@ module EffectivePaypalHelper
31
31
  cert_id: EffectiveOrders.paypal[:cert_id],
32
32
  currency_code: EffectiveOrders.paypal[:currency],
33
33
  invoice: order.id,
34
- amount: (order.subtotal / 100.0).round(2),
35
- tax_cart: (order.tax / 100.0).round(2)
34
+ amount: '%.2f' % (order.amount_owing / 100.0),
35
+ tax_cart: '%.2f' % ((order.tax + order.surcharge_tax) / 100.0)
36
36
  }
37
37
 
38
- order.order_items.each_with_index do |item, x|
39
- values["item_number_#{x+1}"] = x+1
40
- values["item_name_#{x+1}"] = item.name
41
- values["quantity_#{x+1}"] = item.quantity
42
- values["amount_#{x+1}"] = '%.2f' % (item.price / 100.0)
43
- values["tax_#{x+1}"] = '%.2f' % ((item.tax / 100.0) / item.quantity) # Tax for 1 of these items
38
+ number = 0
39
+
40
+ order.order_items.each do |item|
41
+ number += 1
42
+
43
+ values["item_number_#{number}"] = number
44
+ values["item_name_#{number}"] = item.name
45
+ values["quantity_#{number}"] = item.quantity
46
+ values["amount_#{number}"] = '%.2f' % (item.price / 100.0)
47
+ values["tax_#{number}"] = '%.2f' % ((item.tax / 100.0) / item.quantity) # Tax for 1 of these items
48
+ end
49
+
50
+ # Credit Card Surcharge
51
+ if order.surcharge != 0
52
+ number += 1
53
+
54
+ values["item_number_#{number}"] = number
55
+ values["item_name_#{number}"] = 'Credit Card Surcharge'
56
+ values["quantity_#{number}"] = 1
57
+ values["amount_#{number}"] = '%.2f' % (order.surcharge / 100.0)
58
+ values["tax_#{number}"] = '%.2f' % (order.surcharge_tax / 100.0)
44
59
  end
45
60
 
46
61
  signed = OpenSSL::PKCS7::sign(OpenSSL::X509::Certificate.new(APP_CERT_PEM), OpenSSL::PKey::RSA.new(APP_KEY_PEM, ''), values.map { |k, v| "#{k}=#{v}" }.join("\n"), [], OpenSSL::PKCS7::BINARY)
@@ -60,12 +60,12 @@ module EffectiveStripeHelper
60
60
  customer.create_stripe_customer! # Only creates if customer not already present
61
61
 
62
62
  payment = {
63
- amount: order.total,
63
+ amount: order.total_with_surcharge,
64
64
  currency: EffectiveOrders.stripe[:currency],
65
65
  customer: customer.stripe_customer_id,
66
66
  payment_method: customer.payment_method_id.presence,
67
67
  description: stripe_order_description(order),
68
- metadata: { order_id: order.id },
68
+ metadata: { order_id: order.id }
69
69
  }
70
70
 
71
71
  token_required = customer.token_required?
@@ -52,11 +52,17 @@ module Effective
52
52
  payment_provider :string
53
53
  payment_card :string
54
54
 
55
- tax_rate :decimal, precision: 6, scale: 3
55
+ tax_rate :decimal, precision: 6, scale: 3
56
+ surcharge_percent :decimal, precision: 6, scale: 3
56
57
 
57
- subtotal :integer
58
- tax :integer
59
- total :integer
58
+ subtotal :integer # Sum of items subtotal
59
+ tax :integer # Tax on subtotal
60
+ amount_owing :integer # Subtotal + Tax
61
+
62
+ surcharge :integer # Credit Card Surcharge
63
+ surcharge_tax :integer # Tax on surcharge
64
+
65
+ total :integer # Subtotal + Tax + Surcharge + Surcharge Tax
60
66
 
61
67
  timestamps
62
68
  end
@@ -88,7 +94,8 @@ module Effective
88
94
  before_validation { assign_email }
89
95
  before_validation { assign_user_address }
90
96
  before_validation { assign_billing_name }
91
- before_validation { assign_order_totals }
97
+ before_validation { assign_order_values }
98
+ before_validation { assign_order_charges }
92
99
  end
93
100
 
94
101
  # Order validations
@@ -119,7 +126,7 @@ module Effective
119
126
  end
120
127
 
121
128
  # User validations -- An admin skips these when working in the admin/ namespace
122
- with_options unless: -> { pending? || skip_buyer_validations? || purchased? } do
129
+ with_options(unless: -> { pending? || skip_buyer_validations? || purchased? }) do
123
130
  validates :tax_rate, presence: { message: "can't be determined based on billing address" }
124
131
  validates :tax, presence: true
125
132
 
@@ -129,7 +136,7 @@ module Effective
129
136
  end
130
137
 
131
138
  # When Purchased
132
- with_options if: -> { purchased? } do
139
+ with_options(if: -> { purchased? }) do
133
140
  validates :purchased_at, presence: true
134
141
  validates :payment, presence: true
135
142
 
@@ -137,7 +144,7 @@ module Effective
137
144
  validates :payment_card, presence: true
138
145
  end
139
146
 
140
- with_options if: -> { deferred? } do
147
+ with_options(if: -> { deferred? }) do
141
148
  validates :payment_provider, presence: true
142
149
 
143
150
  validate do
@@ -145,11 +152,18 @@ module Effective
145
152
  end
146
153
  end
147
154
 
148
- before_save(if: -> { state_was == EffectiveOrders::PURCHASED }) do
149
- raise EffectiveOrders::AlreadyPurchasedException.new('cannot unpurchase an order') unless purchased?
150
- end
155
+ # Sanity check
156
+ before_save(if: -> { was_purchased? }) do
157
+ raise('cannot unpurchase an order') unless purchased?
158
+
159
+ raise('cannot change subtotal of a purchased order') if changes[:subtotal].present?
160
+
161
+ raise('cannot change tax of a purchased order') if changes[:tax].present?
162
+ raise('cannot change tax of a purchased order') if changes[:tax_rate].present?
163
+
164
+ raise('cannot change surcharge of a purchased order') if changes[:surcharge].present?
165
+ raise('cannot change surcharge percent of a purchased order') if changes[:surcharge_percent].present?
151
166
 
152
- before_save(if: -> { done? }) do
153
167
  raise('cannot change total of a purchased order') if changes[:total].present?
154
168
  end
155
169
 
@@ -213,9 +227,7 @@ module Effective
213
227
  removed.each { |order_item| order_item.mark_for_destruction }
214
228
 
215
229
  # Make sure to reset stored aggregates
216
- self.total = nil
217
- self.subtotal = nil
218
- self.tax = nil
230
+ assign_attributes(subtotal: nil, tax_rate: nil, tax: nil, surcharge_percent: nil, surcharge: nil, total: nil)
219
231
 
220
232
  removed.length == 1 ? removed.first : removed
221
233
  end
@@ -259,9 +271,7 @@ module Effective
259
271
  end.compact
260
272
 
261
273
  # Make sure to reset stored aggregates
262
- self.total = nil
263
- self.subtotal = nil
264
- self.tax = nil
274
+ assign_attributes(subtotal: nil, tax_rate: nil, tax: nil, surcharge_percent: nil, surcharge: nil, total: nil)
265
275
 
266
276
  retval = cart_items.map do |item|
267
277
  order_items.build(
@@ -311,24 +321,14 @@ module Effective
311
321
  end
312
322
 
313
323
  def total_label
314
- if refund? && purchased?
315
- 'Total Paid'
316
- elsif purchased?
317
- 'Total Paid'
318
- elsif refund? && (pending? || confirmed?)
319
- 'Total Due'
320
- elsif (pending? || confirmed?)
321
- 'Total Due'
322
- else
323
- 'Total'
324
- end
324
+ purchased? ? 'Total Paid' : 'Total Due'
325
325
  end
326
326
 
327
327
  # Visa - 1234
328
328
  def payment_method
329
329
  return nil unless purchased?
330
330
 
331
- provider = payment_provider if ['cheque', 'etransfer', 'phone'].include?(payment_provider)
331
+ provider = payment_provider if ['cheque', 'etransfer', 'phone', 'credit card'].include?(payment_provider)
332
332
 
333
333
  # Normalize payment card
334
334
  card = case payment_card.to_s.downcase.gsub(' ', '').strip
@@ -411,6 +411,18 @@ module Effective
411
411
  false
412
412
  end
413
413
 
414
+ def was_purchased?
415
+ state_was == EffectiveOrders::PURCHASED
416
+ end
417
+
418
+ def purchased_with_credit_card?
419
+ purchased? && EffectiveOrders.credit_card_payment_providers.include?(payment_provider)
420
+ end
421
+
422
+ def purchased_without_credit_card?
423
+ purchased? && EffectiveOrders.credit_card_payment_providers.exclude?(payment_provider)
424
+ end
425
+
414
426
  def declined?
415
427
  state == EffectiveOrders::DECLINED
416
428
  end
@@ -424,7 +436,7 @@ module Effective
424
436
  end
425
437
 
426
438
  def subtotal
427
- self[:subtotal] || present_order_items.map { |oi| oi.subtotal }.sum
439
+ self[:subtotal] || get_subtotal()
428
440
  end
429
441
 
430
442
  def tax_rate
@@ -435,8 +447,32 @@ module Effective
435
447
  self[:tax] || get_tax()
436
448
  end
437
449
 
450
+ def amount_owing
451
+ self[:amount_owing] || get_amount_owing()
452
+ end
453
+
454
+ def surcharge_percent
455
+ self[:surcharge_percent] || get_surcharge_percent()
456
+ end
457
+
458
+ def surcharge
459
+ self[:surcharge] || get_surcharge()
460
+ end
461
+
462
+ def surcharge_tax
463
+ self[:surcharge_tax] || get_surcharge_tax()
464
+ end
465
+
438
466
  def total
439
- (self[:total] || (subtotal + tax.to_i)).to_i
467
+ self[:total] || get_total()
468
+ end
469
+
470
+ def total_with_surcharge
471
+ get_total_with_surcharge()
472
+ end
473
+
474
+ def total_without_surcharge
475
+ get_total_without_surcharge()
440
476
  end
441
477
 
442
478
  def free?
@@ -526,12 +562,13 @@ module Effective
526
562
 
527
563
  # Call this as a way to skip over non consequential orders
528
564
  # And mark some purchasables purchased
565
+ # This is different than the Mark as Paid payment processor
529
566
  def mark_as_purchased!
530
567
  purchase!(skip_buyer_validations: true, email: false, skip_quickbooks: true)
531
568
  end
532
569
 
533
570
  # Effective::Order.new(items: Product.first, user: User.first).purchase!(email: false)
534
- def purchase!(payment: 'none', provider: 'none', card: 'none', email: true, skip_buyer_validations: false, skip_quickbooks: false)
571
+ def purchase!(payment: nil, provider: nil, card: nil, email: true, skip_buyer_validations: false, skip_quickbooks: false)
535
572
  return true if purchased?
536
573
 
537
574
  # Assign attributes
@@ -539,12 +576,16 @@ module Effective
539
576
  state: EffectiveOrders::PURCHASED,
540
577
  skip_buyer_validations: skip_buyer_validations,
541
578
 
542
- payment_provider: provider,
543
- payment_card: (card.presence || 'none'),
579
+ payment: payment_to_h(payment.presence || 'none'),
544
580
  purchased_at: (purchased_at.presence || Time.zone.now),
545
- payment: payment_to_h(payment)
581
+
582
+ payment_provider: (provider.presence || 'none'),
583
+ payment_card: (card.presence || 'none')
546
584
  )
547
585
 
586
+ # Updates surcharge and total based on payment_provider
587
+ assign_order_charges()
588
+
548
589
  begin
549
590
  Effective::Order.transaction do
550
591
  run_purchasable_callbacks(:before_purchase)
@@ -600,7 +641,7 @@ module Effective
600
641
  def decline!(payment: 'none', provider: 'none', card: 'none', validate: true)
601
642
  return false if declined?
602
643
 
603
- raise EffectiveOrders::AlreadyPurchasedException.new('order already purchased') if purchased?
644
+ raise('order already purchased') if purchased?
604
645
 
605
646
  error = nil
606
647
 
@@ -664,6 +705,10 @@ module Effective
664
705
 
665
706
  protected
666
707
 
708
+ def get_subtotal
709
+ present_order_items.map { |oi| oi.subtotal }.sum
710
+ end
711
+
667
712
  def get_tax_rate
668
713
  rate = instance_exec(self, &EffectiveOrders.order_tax_rate_method).to_f
669
714
 
@@ -675,10 +720,49 @@ module Effective
675
720
  end
676
721
 
677
722
  def get_tax
678
- return nil unless tax_rate.present?
723
+ return 0 unless tax_rate.present?
679
724
  present_order_items.reject { |oi| oi.tax_exempt? }.map { |oi| (oi.subtotal * (tax_rate / 100.0)).round(0).to_i }.sum
680
725
  end
681
726
 
727
+ def get_amount_owing
728
+ subtotal + tax
729
+ end
730
+
731
+ def get_surcharge_percent
732
+ percent = EffectiveOrders.credit_card_surcharge_percent.to_f
733
+ return nil unless percent > 0.0
734
+
735
+ return 0.0 if purchased_without_credit_card?
736
+
737
+ if (percent > 10.0 || percent < 0.5)
738
+ raise "expected EffectiveOrders.credit_card_surcharge to return a value between 10.0 (10%) and 0.5 (0.5%) or nil. Received #{percent}. Please return 2.5 for 2.5% surcharge."
739
+ end
740
+
741
+ percent
742
+ end
743
+
744
+ def get_surcharge
745
+ return 0 unless surcharge_percent.present?
746
+ ((subtotal + tax) * (surcharge_percent / 100.0)).round(0).to_i
747
+ end
748
+
749
+ def get_surcharge_tax
750
+ return 0 unless tax_rate.present?
751
+ (surcharge * (tax_rate / 100.0)).round(0).to_i
752
+ end
753
+
754
+ def get_total
755
+ subtotal + tax + surcharge + surcharge_tax
756
+ end
757
+
758
+ def get_total_with_surcharge
759
+ subtotal + tax + surcharge + surcharge_tax
760
+ end
761
+
762
+ def get_total_without_surcharge
763
+ subtotal + tax
764
+ end
765
+
682
766
  private
683
767
 
684
768
  def present_order_items
@@ -707,18 +791,31 @@ module Effective
707
791
  end
708
792
  end
709
793
 
710
- # This overwrites the prices, taxes, etc on every save.
711
- def assign_order_totals
794
+ # This overwrites the prices, taxes, surcharge, etc on every save.
795
+ # Does not get run from the before_validate on purchase.
796
+ def assign_order_values
712
797
  # Copies prices from purchasable into order items
713
798
  present_order_items.each { |oi| oi.assign_purchasable_attributes }
714
799
 
715
- # Sum of order item subtotals
716
- subtotal = present_order_items.map { |oi| oi.subtotal }.sum
800
+ # Calculated from each item
801
+ self.subtotal = get_subtotal()
717
802
 
718
- self.subtotal = subtotal
803
+ # We only know tax if there is a billing address
719
804
  self.tax_rate = get_tax_rate()
720
805
  self.tax = get_tax()
721
- self.total = subtotal + (tax || 0)
806
+
807
+ # Subtotal + Tax
808
+ self.amount_owing = get_amount_owing()
809
+ end
810
+
811
+ def assign_order_charges
812
+ # We only apply surcharge for credit card orders. But we have to display and calculate for non purchased orders
813
+ self.surcharge_percent = get_surcharge_percent()
814
+ self.surcharge = get_surcharge()
815
+ self.surcharge_tax = get_surcharge_tax()
816
+
817
+ # Subtotal + Tax + Surcharge + Surcharge Tax
818
+ self.total = get_total()
722
819
  end
723
820
 
724
821
  def update_purchasables_purchased_order!
@@ -32,7 +32,7 @@ module Effective
32
32
  ((quantity || 0) > 1 ? "#{quantity}x #{name}" : name) || 'order item'
33
33
  end
34
34
 
35
- # This method is called in a before_validation in order.assign_order_totals()
35
+ # This method is called in a before_validation in order.assign_order_values()
36
36
  def assign_purchasable_attributes
37
37
  assign_attributes(name: purchasable.purchasable_name, price: purchasable.price, tax_exempt: purchasable.tax_exempt) if purchasable
38
38
  end
@@ -1,67 +1,72 @@
1
- - include_quantity_column = order.order_items.any? { |order_item| order_item.quantity > 1 }
2
- - include_download_column = order.purchased? && order.order_items.any? { |order_item| order_item.purchased_download_url.present? }
3
-
4
1
  .effective-order-items
5
2
  %table.table
6
3
  %thead
7
4
  %tr
8
- - if include_quantity_column
9
- %th.quantity Qty
10
-
11
- - if include_download_column
12
- %th.download Download
13
-
5
+ %th.quantity Qty
14
6
  %th.item= order.order_items.length > 1 ? 'Items' : 'Item'
15
7
  %th.price Price
16
8
 
17
9
  %tbody
18
10
  - order.order_items.each do |item|
19
11
  %tr
20
- - if include_quantity_column
21
- %td.quantity= item.quantity
12
+ %td.quantity= item.quantity
13
+
14
+ %td.item
15
+ = item.name.html_safe
22
16
 
23
- - if include_download_column
24
- %td.download
25
- - if item.purchased_download_url.present?
26
- = link_to 'download', item.purchased_download_url
27
- - else
28
- = '-'
17
+ - if order.purchased? && item.purchased_download_url.present?
18
+ = link_to 'Download', item.purchased_download_url
29
19
 
30
- %td.item= item.name.html_safe
31
20
  %td.price= price_to_currency(item.subtotal)
32
21
 
22
+ %table.table
33
23
  %tfoot
34
- - if order.tax.to_i != 0 || order.tax_rate == nil
35
- %tr
36
- - if include_quantity_column
37
- %th.quantity
38
-
39
- - if include_download_column
40
- %th.download
24
+ %tr
25
+ %th
26
+ %td.subtotal Subtotal
27
+ %td.price.subtotal-price= price_to_currency(order.subtotal)
41
28
 
42
- %th.subtotal Subtotal
43
- %td.price.subtotal-price= price_to_currency(order.subtotal)
29
+ - if order.tax_rate.blank?
30
+ -# Nothing to do. We can't display Tax, Total or Credit Card Surcharge (which is taxed) yet.
44
31
 
45
- - if order.tax.to_i != 0
32
+ - elsif order.tax_rate.present? && order.surcharge_percent.to_f > 0.0
46
33
  %tr
47
- - if include_quantity_column
48
- %th.quantity
34
+ %th
35
+ %td.tax Tax (#{rate_to_percentage(order.tax_rate)})
36
+ %td.price.tax-price= price_to_currency(order.tax)
49
37
 
50
- - if include_download_column
51
- %th.download
38
+ %tr.single-line
39
+ %th
40
+ %td.amount-owing Amount owing before Credit Card Processing Fee
41
+ %td.price.amount-owing-price= price_to_currency(order.amount_owing)
52
42
 
53
- %th.tax Tax (#{tax_rate_to_percentage(order.tax_rate)})
54
- %td.price.tax-price= price_to_currency(order.tax)
43
+ %tr
44
+ %th
45
+ %td
46
+ %td
47
+
48
+ %tr
49
+ %th
50
+ %td.surcharge Credit Card Processing Fee (#{rate_to_percentage(order.surcharge_percent)}) on #{price_to_currency(order.amount_owing)}
51
+ %td.price.surcharge-price= price_to_currency(order.surcharge)
55
52
 
56
- - if order.tax_rate.present?
57
53
  %tr
58
- - if include_quantity_column
59
- %th.quantity
54
+ %th
55
+ %td.surcharge-tax Tax (#{rate_to_percentage(order.tax_rate)}) on Credit Card Processing Fee
56
+ %td.price.surcharge-tax-price= price_to_currency(order.surcharge_tax)
60
57
 
61
- - if include_download_column
62
- %th.download
58
+ %tr.double-line
59
+ %th
60
+ %td.total Total amount charged to credit card
61
+ %td.price.total-price= price_to_currency(order.total)
63
62
 
64
- %th.total
65
- = order.total_label
63
+ - elsif order.tax_rate.present? && !(order.surcharge_percent.to_f > 0.0)
64
+ %tr
65
+ %th
66
+ %td.tax Tax (#{rate_to_percentage(order.tax_rate)})
67
+ %td.price.tax-price= price_to_currency(order.tax)
66
68
 
69
+ %tr.single-line
70
+ %th
71
+ %td.total= order.total_label
67
72
  %td.price.total-price= price_to_currency(order.total)
@@ -0,0 +1,20 @@
1
+ .effective-order-items
2
+ %table.table
3
+ %thead
4
+ %tr
5
+ %th.quantity Qty
6
+ %th.item= order.order_items.length > 1 ? 'Items' : 'Item'
7
+ %th.price Price
8
+
9
+ %tbody
10
+ - order.order_items.each do |item|
11
+ %tr
12
+ %td.quantity= item.quantity
13
+
14
+ %td.item
15
+ = item.name.html_safe
16
+
17
+ - if order.purchased? && item.purchased_download_url.present?
18
+ = link_to 'Download', item.purchased_download_url
19
+
20
+ %td.price= price_to_currency(item.subtotal)
@@ -7,7 +7,8 @@
7
7
  = effective_form_with(model: order, url: effective_orders.mark_as_paid_order_path(order), method: :post) do |f|
8
8
  .row
9
9
  .col-6
10
- = f.select :payment_provider, EffectiveOrders.admin_payment_providers, required: true
10
+ - collection = admin_mark_as_paid_payment_providers()
11
+ = f.select :payment_provider, collection, required: true, grouped: collection.kind_of?(Hash)
11
12
 
12
13
  = f.text_field :payment_card,
13
14
  label: 'Payment card type, cheque, e-transfer or transaction number',
@@ -4,15 +4,24 @@
4
4
  %meta{:content => 'text/html; charset=UTF-8', 'http-equiv' => 'Content-Type'}
5
5
 
6
6
  :css
7
+ .effective-order-header h1 { margin: 0; }
8
+ .effective-order-shipping { padding-top: 12px; padding-bottom: 12px; }
9
+
7
10
  .effective-order table.table { min-width: 650px; vertical-align: top; border: 0; }
8
- .effective-order table.table td { border: 0; vertical-align: top; }
9
11
  .effective-order { text-align: left; }
10
- .effective-order .price { text-align: right; }
11
- .effective-order tfoot th { text-align: right; }
12
- .effective-order tfoot tr:first-child td { padding-top: 24px; }
13
- .effective-order tfoot tr:first-child th { padding-top: 24px; }
14
- .effective-order-shipping { padding-top: 12px; padding-bottom: 12px; }
15
- .effective-order-header h1 { margin: 0; }
12
+ .effective-order .price { text-align: right; padding-left: 24px; }
13
+
14
+ .effective-order tfoot th { text-align: right; white-space: nowrap; }
15
+ .effective-order tfoot td { text-align: right; white-space: nowrap; }
16
+
17
+ .effective-order tfoot th { width: 100%; }
18
+
19
+ .effective-order tfoot tr.single-line td { border-top: solid 1px; }
20
+ .effective-order tfoot tr.double-line td { border-top: double 4px; }
21
+
22
+ .effective-order tfoot .amount-owing { font-style: italic; }
23
+ .effective-order tfoot .total { font-style: italic; }
24
+
16
25
  @media print { .effective-page-break { page-break-inside: avoid; page-break-after: always; min-height: 1px; } }
17
26
 
18
27
  %body{style: 'background: #fff;'}
@@ -47,6 +47,12 @@ EffectiveOrders.setup do |config|
47
47
  # An order must have a tax rate (even if the value is 0) to be purchased
48
48
  config.order_tax_rate_method = Proc.new { |order| Effective::TaxRateCalculator.new(order: order).tax_rate }
49
49
 
50
+ # Credit Card Surcharge
51
+ # Will be applied to all orders based off the after-tax total.
52
+ # Use 2.4 for 2.4% or nil for none
53
+ config.credit_card_surcharge_percent = nil
54
+ config.credit_card_surcharge_qb_item_name = 'Credit Card Surcharge'
55
+
50
56
  # Minimum Charge
51
57
  # Prevent orders less than this value from being purchased
52
58
  # Stripe doesn't allow orders less than $0.50
@@ -23,9 +23,13 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
23
23
  t.string :payment_card
24
24
 
25
25
  t.decimal :tax_rate, :precision => 6, :scale => 3
26
+ t.decimal :surcharge_percent, :precision => 6, :scale => 3
26
27
 
27
28
  t.integer :subtotal
28
29
  t.integer :tax
30
+ t.integer :amount_owing
31
+ t.integer :surcharge
32
+ t.integer :surcharge_tax
29
33
  t.integer :total
30
34
 
31
35
  t.timestamps
@@ -1,3 +1,3 @@
1
1
  module EffectiveOrders
2
- VERSION = '5.9.3'.freeze
2
+ VERSION = '6.0.0'.freeze
3
3
  end
@@ -39,6 +39,7 @@ module EffectiveOrders
39
39
  :billing_address, :shipping_address,
40
40
  :collect_note, :collect_note_required, :collect_note_message,
41
41
  :terms_and_conditions, :terms_and_conditions_label, :minimum_charge,
42
+ :credit_card_surcharge_percent, :credit_card_surcharge_qb_item_name,
42
43
 
43
44
  # Mailer
44
45
  :mailer, :parent_mailer, :deliver_method, :mailer_layout, :mailer_sender, :mailer_admin, :mailer_subject,
@@ -166,7 +167,7 @@ module EffectiveOrders
166
167
  #('pretend' if pretend?),
167
168
  #('refund' if refund?),
168
169
  ('stripe' if stripe?),
169
- ('other' if mark_as_paid?),
170
+ ('other (non credit card)' if mark_as_paid?),
170
171
  'none'
171
172
  ].compact
172
173
  end
@@ -175,6 +176,10 @@ module EffectiveOrders
175
176
  [('cheque' if cheque?), ('etransfer' if etransfer?), ('phone' if phone?)].compact
176
177
  end
177
178
 
179
+ def self.credit_card_payment_providers
180
+ ['credit card', 'moneris', 'moneris_checkout', 'paypal', 'stripe']
181
+ end
182
+
178
183
  def self.qb_sync?
179
184
  use_effective_qb_sync && defined?(EffectiveQbSync)
180
185
  end
@@ -285,7 +290,6 @@ module EffectiveOrders
285
290
  end
286
291
 
287
292
  class SoldOutException < Exception; end
288
- class AlreadyPurchasedException < Exception; end
289
293
 
290
294
  def self.gem_path
291
295
  __dir__.chomp('/lib')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_orders
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.9.3
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-29 00:00:00.000000000 Z
11
+ date: 2022-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -269,6 +269,7 @@ files:
269
269
  - app/views/effective/orders/_order_payment.html.haml
270
270
  - app/views/effective/orders/_order_shipping.html.haml
271
271
  - app/views/effective/orders/_orders_table.html.haml
272
+ - app/views/effective/orders/_purchasables.html.haml
272
273
  - app/views/effective/orders/cheque/_form.html.haml
273
274
  - app/views/effective/orders/declined.html.haml
274
275
  - app/views/effective/orders/deferred.html.haml