effective_orders 5.9.3 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/effective_orders/_order.scss +20 -14
- data/app/controllers/effective/providers/stripe.rb +30 -31
- data/app/datatables/admin/effective_orders_datatable.rb +7 -8
- data/app/datatables/effective_orders_datatable.rb +8 -6
- data/app/helpers/effective_carts_helper.rb +3 -2
- data/app/helpers/effective_moneris_checkout_helper.rb +1 -2
- data/app/helpers/effective_orders_helper.rb +23 -0
- data/app/helpers/effective_paypal_helper.rb +23 -8
- data/app/helpers/effective_stripe_helper.rb +2 -2
- data/app/models/effective/order.rb +141 -44
- data/app/models/effective/order_item.rb +1 -1
- data/app/views/effective/orders/_order_items.html.haml +46 -41
- data/app/views/effective/orders/_purchasables.html.haml +20 -0
- data/app/views/effective/orders/mark_as_paid/_form.html.haml +2 -1
- data/app/views/layouts/effective_orders_mailer_layout.html.haml +16 -7
- data/config/effective_orders.rb +6 -0
- data/db/migrate/01_create_effective_orders.rb.erb +4 -0
- data/lib/effective_orders/version.rb +1 -1
- data/lib/effective_orders.rb +6 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f548373346f66befb20e325b166d275eff0444b9b8d2865f87c37d8116d32b0d
|
4
|
+
data.tar.gz: c2cdbd728306010ba79e3847fd59578e3b51afbf2764021b3581767bad0d6831
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 027327c45635652ad18bf89f0597d3c22156adf9ba04a527014c5aae2daed01a92f50dd6d134cf7afc5a34b6ca02daae7b69e73fd801269bf22f75e07f61cc16
|
7
|
+
data.tar.gz: 54576594dab1cd9a569e7c37aab4cf945b8670628d92868a85a8d068a212a8f175cf31c0955fbb933fb618efde82cd9a0d090cb8a83446c876ea74b0a9b10c0a
|
@@ -1,31 +1,37 @@
|
|
1
1
|
.effective-order {
|
2
2
|
table {
|
3
|
-
|
4
|
-
margin-bottom: 25px;
|
3
|
+
.quantity { width: 0%; }
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
.price {
|
6
|
+
text-align: right;
|
7
|
+
padding-left: 2rem;
|
8
|
+
}
|
10
9
|
|
11
|
-
|
10
|
+
tfoot {
|
11
|
+
th, td {
|
12
12
|
border: none;
|
13
13
|
text-align: right;
|
14
|
-
|
14
|
+
white-space: nowrap;
|
15
15
|
}
|
16
16
|
|
17
|
-
|
18
|
-
|
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
|
-
.
|
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.
|
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?
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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 :
|
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 :
|
75
|
-
|
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 :
|
49
|
-
|
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(
|
106
|
+
render('effective/carts/cart', cart: cart)
|
107
107
|
end
|
108
108
|
|
109
109
|
def render_purchasables(*purchasables)
|
110
|
-
|
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:
|
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.
|
35
|
-
tax_cart: (order.tax / 100.0)
|
34
|
+
amount: '%.2f' % (order.amount_owing / 100.0),
|
35
|
+
tax_cart: '%.2f' % ((order.tax + order.surcharge_tax) / 100.0)
|
36
36
|
}
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
values["
|
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.
|
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
|
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
|
-
|
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 {
|
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
|
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
|
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
|
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
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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] ||
|
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
|
-
|
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:
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
#
|
716
|
-
subtotal =
|
800
|
+
# Calculated from each item
|
801
|
+
self.subtotal = get_subtotal()
|
717
802
|
|
718
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
21
|
-
|
12
|
+
%td.quantity= item.quantity
|
13
|
+
|
14
|
+
%td.item
|
15
|
+
= item.name.html_safe
|
22
16
|
|
23
|
-
|
24
|
-
|
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
|
-
|
35
|
-
%
|
36
|
-
|
37
|
-
|
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
|
-
|
43
|
-
|
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
|
-
-
|
32
|
+
- elsif order.tax_rate.present? && order.surcharge_percent.to_f > 0.0
|
46
33
|
%tr
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
54
|
-
%
|
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
|
-
|
59
|
-
|
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
|
-
|
62
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
12
|
-
.effective-order tfoot
|
13
|
-
.effective-order tfoot
|
14
|
-
|
15
|
-
.effective-order
|
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;'}
|
data/config/effective_orders.rb
CHANGED
@@ -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
|
data/lib/effective_orders.rb
CHANGED
@@ -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:
|
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
|
+
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
|