effective_orders 1.8.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +150 -60
- data/Rakefile +0 -3
- data/active_admin/effective_orders.rb +14 -9
- data/app/assets/javascripts/effective_orders.js +2 -0
- data/app/assets/javascripts/effective_orders/{stripe_charges.js.coffee → providers/stripe_charges.js.coffee} +1 -1
- data/app/assets/javascripts/effective_orders/{stripe_subscriptions.js.coffee → providers/stripe_subscriptions.js.coffee} +1 -1
- data/app/assets/stylesheets/effective_orders/_order.scss +9 -3
- data/app/controllers/admin/orders_controller.rb +87 -3
- data/app/controllers/concerns/acts_as_active_admin_controller.rb +2 -1
- data/app/controllers/effective/carts_controller.rb +4 -2
- data/app/controllers/effective/orders_controller.rb +101 -60
- data/app/controllers/effective/providers/app_checkout.rb +10 -2
- data/app/controllers/effective/providers/ccbill.rb +34 -0
- data/app/controllers/effective/providers/cheque.rb +30 -0
- data/app/controllers/effective/providers/moneris.rb +7 -7
- data/app/controllers/effective/providers/paypal.rb +7 -5
- data/app/controllers/effective/providers/pretend.rb +22 -0
- data/app/controllers/effective/providers/stripe.rb +26 -24
- data/app/controllers/effective/webhooks_controller.rb +1 -1
- data/app/helpers/effective_carts_helper.rb +59 -29
- data/app/helpers/effective_ccbill_helper.rb +25 -0
- data/app/helpers/effective_orders_helper.rb +50 -41
- data/app/helpers/effective_paypal_helper.rb +11 -11
- data/app/mailers/effective/orders_mailer.rb +95 -20
- data/app/models/concerns/acts_as_purchasable.rb +14 -22
- data/app/models/effective/cart.rb +9 -15
- data/app/models/effective/cart_item.rb +15 -13
- data/app/models/effective/customer.rb +9 -9
- data/app/models/effective/datatables/order_items.rb +14 -8
- data/app/models/effective/datatables/orders.rb +56 -69
- data/app/models/effective/order.rb +250 -139
- data/app/models/effective/order_item.rb +23 -16
- data/app/models/effective/product.rb +23 -0
- data/app/models/effective/providers/ccbill_postback.rb +85 -0
- data/app/models/effective/{stripe_charge.rb → providers/stripe_charge.rb} +1 -1
- data/app/models/effective/subscription.rb +16 -12
- data/app/models/effective/tax_rate_calculator.rb +45 -0
- data/app/views/admin/customers/index.html.haml +2 -5
- data/app/views/admin/order_items/index.html.haml +2 -5
- data/app/views/admin/orders/_actions.html.haml +2 -0
- data/app/views/admin/orders/_form.html.haml +28 -0
- data/app/views/admin/orders/_order_item_fields.html.haml +9 -0
- data/app/views/admin/orders/index.html.haml +9 -5
- data/app/views/admin/orders/new.html.haml +3 -0
- data/app/views/effective/carts/_cart.html.haml +3 -12
- data/app/views/effective/carts/_cart_actions.html.haml +4 -0
- data/app/views/effective/carts/show.html.haml +10 -12
- data/app/views/effective/orders/_checkout_step1.html.haml +46 -0
- data/app/views/effective/orders/_checkout_step2.html.haml +33 -0
- data/app/views/effective/orders/_order.html.haml +2 -0
- data/app/views/effective/orders/_order_actions.html.haml +10 -5
- data/app/views/effective/orders/_order_footer.html.haml +1 -0
- data/app/views/effective/orders/_order_items.html.haml +11 -9
- data/app/views/effective/orders/_order_note.html.haml +8 -0
- data/app/views/effective/orders/_order_note_fields.html.haml +5 -0
- data/app/views/effective/orders/_order_shipping.html.haml +4 -4
- data/app/views/effective/orders/_order_user_fields.html.haml +1 -0
- data/app/views/effective/orders/_orders_table.html.haml +27 -0
- data/app/views/effective/orders/ccbill/_form.html.haml +24 -0
- data/app/views/effective/orders/checkout_step1.html.haml +3 -0
- data/app/views/effective/orders/checkout_step2.html.haml +3 -0
- data/app/views/effective/orders/cheque/_form.html.haml +2 -0
- data/app/views/effective/orders/cheque/pay_by_cheque.html.haml +9 -0
- data/app/views/effective/orders/declined.html.haml +4 -2
- data/app/views/effective/orders/my_purchases.html.haml +1 -1
- data/app/views/effective/orders/my_sales.html.haml +1 -1
- data/app/views/effective/orders/purchased.html.haml +3 -2
- data/app/views/effective/orders/show.html.haml +1 -1
- data/app/views/effective/orders/stripe/_form.html.haml +5 -5
- data/app/views/effective/orders_mailer/order_error.html.haml +11 -0
- data/app/views/effective/orders_mailer/payment_request_to_buyer.html.haml +13 -0
- data/app/views/effective/orders_mailer/pending_order_invoice_to_buyer.html.haml +13 -0
- data/app/views/layouts/effective_orders_mailer_layout.html.haml +7 -7
- data/config/routes.rb +39 -24
- data/db/migrate/01_create_effective_orders.rb.erb +20 -1
- data/db/upgrade/03_upgrade_effective_orders_from1x.rb.erb +95 -0
- data/lib/effective_orders.rb +67 -9
- data/lib/effective_orders/app_checkout_service.rb +1 -2
- data/lib/effective_orders/engine.rb +46 -14
- data/lib/effective_orders/version.rb +1 -1
- data/lib/generators/effective_orders/install_generator.rb +1 -0
- data/lib/generators/effective_orders/upgrade_from03x_generator.rb +1 -0
- data/lib/generators/effective_orders/upgrade_from1x_generator.rb +31 -0
- data/lib/generators/templates/effective_orders.rb +131 -66
- data/lib/generators/templates/effective_orders_mailer_preview.rb +28 -13
- data/spec/controllers/admin/orders_controller_spec.rb +242 -0
- data/spec/controllers/ccbill_orders_controller_spec.rb +103 -0
- data/spec/controllers/moneris_orders_controller_spec.rb +23 -23
- data/spec/controllers/orders_controller_spec.rb +167 -79
- data/spec/controllers/stripe_orders_controller_spec.rb +7 -7
- data/spec/dummy/app/models/product.rb +0 -8
- data/spec/dummy/app/models/product_with_float_price.rb +0 -8
- data/spec/dummy/app/models/user.rb +2 -19
- data/spec/dummy/config/application.rb +2 -1
- data/spec/dummy/config/environments/test.rb +1 -0
- data/spec/dummy/config/initializers/effective_orders.rb +109 -64
- data/spec/dummy/db/schema.rb +15 -2
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +0 -258
- data/spec/models/acts_as_purchasable_spec.rb +8 -6
- data/spec/models/factories_spec.rb +7 -1
- data/spec/models/order_item_spec.rb +1 -1
- data/spec/models/order_spec.rb +165 -46
- data/spec/models/stripe_charge_spec.rb +5 -5
- data/spec/spec_helper.rb +2 -0
- data/spec/support/factories.rb +49 -33
- metadata +47 -64
- data/app/views/effective/orders/_checkout_step_1.html.haml +0 -43
- data/app/views/effective/orders/_checkout_step_2.html.haml +0 -25
- data/app/views/effective/orders/_my_purchases.html.haml +0 -17
- data/app/views/effective/orders/checkout.html.haml +0 -3
- data/app/views/effective/orders/new.html.haml +0 -4
- data/spec/dummy/log/development.log +0 -82
@@ -0,0 +1,25 @@
|
|
1
|
+
module EffectiveCcbillHelper
|
2
|
+
def ccbill_form_digest(order)
|
3
|
+
digested_variables = [
|
4
|
+
ccbill_price(order.total),
|
5
|
+
EffectiveOrders.ccbill[:form_period],
|
6
|
+
EffectiveOrders.ccbill[:currency_code],
|
7
|
+
EffectiveOrders.ccbill[:dynamic_pricing_salt]
|
8
|
+
]
|
9
|
+
string = digested_variables.join('')
|
10
|
+
Digest::MD5.hexdigest(string)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ccbill_price(price)
|
14
|
+
number_to_currency(price/100.0, unit: '')
|
15
|
+
end
|
16
|
+
|
17
|
+
def ccbill_customer_name(order, name = :full_name)
|
18
|
+
if order.user.present? && order.user.try(name).present?
|
19
|
+
order.user.public_send(name)
|
20
|
+
elsif order.billing_address.present?
|
21
|
+
order.billing_address.public_send(name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -4,10 +4,12 @@ module EffectiveOrdersHelper
|
|
4
4
|
number_to_currency(price / 100.0)
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def tax_rate_to_percentage(tax_rate)
|
8
|
+
number_to_percentage(tax_rate, strip_insignificant_zeros: true)
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
+
def order_summary(order)
|
12
|
+
order_item_list = content_tag(:ul) do
|
11
13
|
order.order_items.map do |item|
|
12
14
|
content_tag(:li) do
|
13
15
|
title = item.title.split('<br>')
|
@@ -17,6 +19,7 @@ module EffectiveOrdersHelper
|
|
17
19
|
end
|
18
20
|
end.join.html_safe
|
19
21
|
end
|
22
|
+
content_tag(:p, "#{price_to_currency(order.total)} total for #{pluralize(order.num_items, 'item')}:") + order_item_list
|
20
23
|
end
|
21
24
|
|
22
25
|
def order_item_summary(order_item)
|
@@ -33,16 +36,16 @@ module EffectiveOrdersHelper
|
|
33
36
|
case processor
|
34
37
|
when :free
|
35
38
|
'Checkout'
|
36
|
-
when :moneris
|
37
|
-
'Checkout with
|
39
|
+
when :moneris, :stripe, :ccbill
|
40
|
+
'Checkout with credit card'
|
38
41
|
when :paypal
|
39
42
|
'Checkout with PayPal'
|
40
|
-
when :pretend
|
43
|
+
when :pretend
|
41
44
|
EffectiveOrders.allow_pretend_purchase_in_production ? 'Purchase Order' : 'Purchase Order (development only)'
|
42
|
-
when :
|
43
|
-
'
|
45
|
+
when :cheque
|
46
|
+
'Pay by Cheque'
|
44
47
|
when :app_checkout
|
45
|
-
EffectiveOrders.app_checkout[:checkout_label]
|
48
|
+
EffectiveOrders.app_checkout[:checkout_label].presence || 'Checkout'
|
46
49
|
else
|
47
50
|
'Checkout'
|
48
51
|
end
|
@@ -77,53 +80,59 @@ module EffectiveOrdersHelper
|
|
77
80
|
end
|
78
81
|
|
79
82
|
def render_order(order)
|
80
|
-
render(:
|
83
|
+
render(partial: 'effective/orders/order', locals: {order: order})
|
81
84
|
end
|
82
85
|
|
83
|
-
def
|
86
|
+
def render_checkout_step1(order, opts = {})
|
84
87
|
raise ArgumentError.new('unable to checkout an order without a user') unless order.user.present?
|
85
88
|
|
86
|
-
locals = {
|
87
|
-
|
88
|
-
|
89
|
-
|
89
|
+
locals = { purchased_redirect_url: nil, declined_redirect_url: nil }.merge(opts)
|
90
|
+
|
91
|
+
render(partial: 'effective/orders/checkout_step1', locals: locals.merge({order: order}))
|
92
|
+
end
|
93
|
+
alias_method :render_checkout, :render_checkout_step1
|
94
|
+
|
95
|
+
def render_checkout_step2(order, opts = {})
|
96
|
+
raise ArgumentError.new('unable to checkout an order without a user') unless order.user.present?
|
97
|
+
|
98
|
+
locals = { purchased_redirect_url: nil, declined_redirect_url: nil }.merge(opts)
|
90
99
|
|
91
100
|
if order.new_record? || !order.valid?
|
92
|
-
render(:
|
101
|
+
render(partial: 'effective/orders/checkout_step1', locals: locals.merge({order: order}))
|
93
102
|
else
|
94
|
-
render(:
|
103
|
+
render(partial: 'effective/orders/checkout_step2', locals: locals.merge({order: order}))
|
95
104
|
end
|
96
105
|
end
|
97
106
|
|
98
107
|
def link_to_my_purchases(opts = {})
|
99
|
-
options = {
|
100
|
-
|
108
|
+
options = {
|
109
|
+
label: 'My Purchases',
|
110
|
+
class: 'btn btn-default',
|
111
|
+
rel: :nofollow
|
112
|
+
}.merge(opts)
|
113
|
+
|
114
|
+
label = options.delete(:label)
|
115
|
+
options[:class] = ((options[:class] || '') + ' btn-my-purchases')
|
116
|
+
|
117
|
+
link_to(label, effective_orders.my_purchases_path, options)
|
101
118
|
end
|
102
119
|
alias_method :link_to_order_history, :link_to_my_purchases
|
103
120
|
|
104
|
-
def
|
121
|
+
def render_orders_table(user_or_orders, opts = {})
|
105
122
|
if user_or_orders.kind_of?(User)
|
106
123
|
orders = Effective::Order.purchased_by(user_or_orders)
|
107
124
|
elsif user_or_orders.respond_to?(:to_a)
|
108
|
-
|
109
|
-
orders = user_or_orders.to_a.select { |order| order.purchased? }
|
110
|
-
rescue => e
|
111
|
-
raise ArgumentError.new('expecting an instance of User or an array/collection of Effective::Order objects')
|
112
|
-
end
|
125
|
+
orders = user_or_orders.to_a
|
113
126
|
else
|
114
127
|
raise ArgumentError.new('expecting an instance of User or an array/collection of Effective::Order objects')
|
115
128
|
end
|
116
129
|
|
117
|
-
locals
|
118
|
-
:orders => orders,
|
119
|
-
:order_path => effective_orders.order_path(':id') # The :id string will be replaced with the order id
|
120
|
-
}.merge(opts)
|
121
|
-
|
122
|
-
render(:partial => 'effective/orders/my_purchases', :locals => locals)
|
130
|
+
render(partial: 'effective/orders/orders_table', locals: {orders: orders}.merge(opts))
|
123
131
|
end
|
124
132
|
|
125
|
-
alias_method :render_purchases, :
|
126
|
-
alias_method :render_my_purchases, :
|
133
|
+
alias_method :render_purchases, :render_orders_table
|
134
|
+
alias_method :render_my_purchases, :render_orders_table
|
135
|
+
alias_method :render_order_history, :render_orders_table
|
127
136
|
|
128
137
|
# Used by the _payment_details partial
|
129
138
|
def tableize_order_payment(hash, options = {class: 'table table-bordered'})
|
@@ -135,15 +144,15 @@ module EffectiveOrdersHelper
|
|
135
144
|
hash.map do |k, v|
|
136
145
|
content_tag(:tr) do
|
137
146
|
content_tag((options[:th] ? :th : :td), k) +
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
147
|
+
content_tag(:td) do
|
148
|
+
if v.kind_of?(Hash)
|
149
|
+
tableize_order_payment(v, options.merge(th: (options.key?(:sub_th) ? options[:sub_th] : options[:th])))
|
150
|
+
elsif v.kind_of?(Array)
|
151
|
+
'[' + v.join(', ') + ']'
|
152
|
+
else
|
153
|
+
v
|
154
|
+
end
|
145
155
|
end
|
146
|
-
end
|
147
156
|
end
|
148
157
|
end.join.html_safe
|
149
158
|
end
|
@@ -20,17 +20,17 @@ module EffectivePaypalHelper
|
|
20
20
|
raise ArgumentError.new("unable to read EffectiveOrders PayPal app_key #{EffectiveOrders.paypal[:app_key]}") unless APP_KEY_PEM.present?
|
21
21
|
|
22
22
|
values = {
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
23
|
+
business: EffectiveOrders.paypal[:seller_email],
|
24
|
+
custom: EffectiveOrders.paypal[:secret],
|
25
|
+
cmd: '_cart',
|
26
|
+
upload: 1,
|
27
|
+
return: effective_orders.order_purchased_url(order),
|
28
|
+
notify_url: effective_orders.paypal_postback_url,
|
29
|
+
cert_id: EffectiveOrders.paypal[:cert_id],
|
30
|
+
currency_code: EffectiveOrders.paypal[:currency],
|
31
|
+
invoice: order.id,
|
32
|
+
amount: (order.subtotal / 100.0).round(2),
|
33
|
+
tax_cart: (order.tax / 100.0).round(2)
|
34
34
|
}
|
35
35
|
|
36
36
|
order.order_items.each_with_index do |item, x|
|
@@ -1,52 +1,108 @@
|
|
1
1
|
module Effective
|
2
2
|
class OrdersMailer < ActionMailer::Base
|
3
3
|
helper EffectiveOrdersHelper
|
4
|
-
default :from => EffectiveOrders.mailer[:default_from]
|
5
4
|
|
6
5
|
layout EffectiveOrders.mailer[:layout].presence || 'effective_orders_mailer_layout'
|
7
6
|
|
8
|
-
def order_receipt_to_admin(
|
9
|
-
@order =
|
10
|
-
|
7
|
+
def order_receipt_to_admin(order_param)
|
8
|
+
@order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
|
9
|
+
|
10
|
+
mail(
|
11
|
+
to: EffectiveOrders.mailer[:admin_email],
|
12
|
+
from: EffectiveOrders.mailer[:default_from],
|
13
|
+
subject: subject_for_order_receipt_to_admin(@order)
|
14
|
+
)
|
11
15
|
end
|
12
16
|
|
13
|
-
def order_receipt_to_buyer(
|
14
|
-
@order =
|
15
|
-
|
17
|
+
def order_receipt_to_buyer(order_param) # Buyer
|
18
|
+
@order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
|
19
|
+
|
20
|
+
mail(
|
21
|
+
to: @order.user.email,
|
22
|
+
from: EffectiveOrders.mailer[:default_from],
|
23
|
+
subject: subject_for_order_receipt_to_buyer(@order)
|
24
|
+
)
|
16
25
|
end
|
17
26
|
|
18
|
-
def order_receipt_to_seller(
|
19
|
-
@order =
|
27
|
+
def order_receipt_to_seller(order_param, seller, order_items)
|
28
|
+
@order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
|
20
29
|
@user = seller.user
|
21
30
|
@order_items = order_items
|
22
|
-
@subject =
|
31
|
+
@subject = subject_for_order_receipt_to_seller(@order, @order_items, seller.user)
|
32
|
+
|
33
|
+
mail(
|
34
|
+
to: @user.email,
|
35
|
+
from: EffectiveOrders.mailer[:default_from],
|
36
|
+
subject: @subject
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# This is sent when an admin creates a new order or /admin/orders/new
|
41
|
+
# Or uses the order action Send Payment Request
|
42
|
+
def payment_request_to_buyer(order_param)
|
43
|
+
@order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
|
44
|
+
|
45
|
+
mail(
|
46
|
+
to: @order.user.email,
|
47
|
+
from: EffectiveOrders.mailer[:default_from],
|
48
|
+
subject: subject_for_payment_request_to_buyer(@order)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
# This is sent when someone chooses to Pay by Cheque
|
53
|
+
def pending_order_invoice_to_buyer(order_param)
|
54
|
+
@order = (order_param.kind_of?(Effective::Order) ? order_param : Effective::Order.find(order_param))
|
55
|
+
|
56
|
+
mail(
|
57
|
+
to: @order.user.email,
|
58
|
+
from: EffectiveOrders.mailer[:default_from],
|
59
|
+
subject: subject_for_pending_order_invoice_to_buyer(@order)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def order_error(order: nil, error: nil, to: nil, from: nil, subject: nil, template: 'order_error')
|
64
|
+
if order.present?
|
65
|
+
@order = (order.kind_of?(Effective::Order) ? order : Effective::Order.find(order))
|
66
|
+
@subject = (subject || "An error occurred with order: ##{@order.try(:to_param)}")
|
67
|
+
else
|
68
|
+
@subject = (subject || "An order error occurred with an unknown order")
|
69
|
+
end
|
70
|
+
|
71
|
+
@error = error.to_s
|
72
|
+
|
73
|
+
mail(
|
74
|
+
to: (to || EffectiveOrders.mailer[:admin_email]),
|
75
|
+
from: (from || EffectiveOrders.mailer[:default_from]),
|
76
|
+
subject: prefix_subject(@subject),
|
77
|
+
) do |format|
|
78
|
+
format.html { render(template) }
|
79
|
+
end
|
23
80
|
|
24
|
-
mail(:to => @user.email, :subject => @subject)
|
25
81
|
end
|
26
82
|
|
27
83
|
private
|
28
84
|
|
29
|
-
def
|
30
|
-
string_or_callable = EffectiveOrders.mailer[:
|
85
|
+
def subject_for_order_receipt_to_admin(order)
|
86
|
+
string_or_callable = EffectiveOrders.mailer[:subject_for_order_receipt_to_admin]
|
31
87
|
|
32
88
|
if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
|
33
89
|
string_or_callable = self.instance_exec(order, &string_or_callable)
|
34
90
|
end
|
35
91
|
|
36
|
-
prefix_subject(string_or_callable.presence || "Order ##{order.to_param}
|
92
|
+
prefix_subject(string_or_callable.presence || "Order Receipt: ##{order.to_param}")
|
37
93
|
end
|
38
94
|
|
39
|
-
def
|
40
|
-
string_or_callable = EffectiveOrders.mailer[:
|
95
|
+
def subject_for_order_receipt_to_buyer(order)
|
96
|
+
string_or_callable = EffectiveOrders.mailer[:subject_for_order_receipt_to_buyer]
|
41
97
|
|
42
98
|
if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
|
43
99
|
string_or_callable = self.instance_exec(order, &string_or_callable)
|
44
100
|
end
|
45
101
|
|
46
|
-
prefix_subject(string_or_callable.presence || "Order ##{order.to_param}
|
102
|
+
prefix_subject(string_or_callable.presence || "Order Receipt: ##{order.to_param}")
|
47
103
|
end
|
48
104
|
|
49
|
-
def
|
105
|
+
def subject_for_order_receipt_to_seller(order, order_items, seller)
|
50
106
|
string_or_callable = EffectiveOrders.mailer[:subject_for_seller_receipt]
|
51
107
|
|
52
108
|
if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
|
@@ -56,11 +112,30 @@ module Effective
|
|
56
112
|
prefix_subject(string_or_callable.presence || "#{order_items.length} of your products #{order_items.length > 1 ? 'have' : 'has'} been purchased")
|
57
113
|
end
|
58
114
|
|
115
|
+
def subject_for_payment_request_to_buyer(order)
|
116
|
+
string_or_callable = EffectiveOrders.mailer[:subject_for_payment_request_to_buyer]
|
117
|
+
|
118
|
+
if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
|
119
|
+
string_or_callable = self.instance_exec(order, &string_or_callable)
|
120
|
+
end
|
121
|
+
|
122
|
+
prefix_subject(string_or_callable.presence || "Request for Payment: Invoice ##{order.to_param}")
|
123
|
+
end
|
124
|
+
|
125
|
+
def subject_for_pending_order_invoice_to_buyer(order)
|
126
|
+
string_or_callable = EffectiveOrders.mailer[:subject_for_pending_order_invoice_to_buyer]
|
127
|
+
|
128
|
+
if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
|
129
|
+
string_or_callable = self.instance_exec(order, &string_or_callable)
|
130
|
+
end
|
131
|
+
|
132
|
+
prefix_subject(string_or_callable.presence || "Pending Order: ##{order.to_param}")
|
133
|
+
end
|
134
|
+
|
135
|
+
|
59
136
|
def prefix_subject(text)
|
60
137
|
prefix = (EffectiveOrders.mailer[:subject_prefix].to_s rescue '')
|
61
138
|
prefix.present? ? (prefix.chomp(' ') + ' ' + text) : text
|
62
139
|
end
|
63
|
-
|
64
140
|
end
|
65
141
|
end
|
66
|
-
|
@@ -1,33 +1,36 @@
|
|
1
1
|
module ActsAsPurchasable
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
+
mattr_accessor :descendants
|
5
|
+
|
4
6
|
module ActiveRecord
|
5
7
|
def acts_as_purchasable(*options)
|
6
8
|
@acts_as_purchasable = options || []
|
7
9
|
include ::ActsAsPurchasable
|
10
|
+
(ActsAsPurchasable.descendants ||= []) << self
|
8
11
|
end
|
9
12
|
end
|
10
13
|
|
11
14
|
included do
|
12
|
-
has_many :orders, :
|
13
|
-
has_many :order_items, :
|
14
|
-
has_many :cart_items, :
|
15
|
+
has_many :orders, through: :order_items, class_name: 'Effective::Order'
|
16
|
+
has_many :order_items, as: :purchasable, class_name: 'Effective::OrderItem'
|
17
|
+
has_many :cart_items, as: :purchasable, dependent: :delete_all, class_name: 'Effective::CartItem'
|
15
18
|
|
16
|
-
validates_with Effective::SoldOutValidator, :
|
19
|
+
validates_with Effective::SoldOutValidator, on: :create
|
17
20
|
|
18
21
|
# Database max integer value is 2147483647. So let's round that down and use a max/min of $20 million (2000000000)
|
19
|
-
validates :price, :
|
22
|
+
validates :price, presence: true, numericality: { less_than_or_equal_to: 2000000000, message: 'maximum price is $20,000,000' }
|
20
23
|
|
21
|
-
validates :tax_exempt, :
|
24
|
+
validates :tax_exempt, inclusion: {in: [true, false]}
|
22
25
|
|
23
26
|
# These are breaking on the check for quanitty_enabled?. More research is due
|
24
|
-
validates :quantity_purchased, :
|
25
|
-
validates :quantity_max, :
|
27
|
+
validates :quantity_purchased, numericality: {allow_nil: true}, if: proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
|
28
|
+
validates :quantity_max, numericality: {allow_nil: true}, if: proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
|
26
29
|
|
27
|
-
scope :purchased, -> { joins(:order_items).joins(:orders).where(:
|
28
|
-
scope :purchased_by, lambda { |user| joins(:order_items).joins(:orders).where(:
|
30
|
+
scope :purchased, -> { joins(:order_items).joins(:orders).where(orders: {purchase_state: EffectiveOrders::PURCHASED}).uniq }
|
31
|
+
scope :purchased_by, lambda { |user| joins(:order_items).joins(:orders).where(orders: {user_id: user.try(:id), purchase_state: EffectiveOrders::PURCHASED}).uniq }
|
29
32
|
scope :sold, -> { purchased() }
|
30
|
-
scope :sold_by, lambda { |user| joins(:order_items).joins(:orders).where(:
|
33
|
+
scope :sold_by, lambda { |user| joins(:order_items).joins(:orders).where(order_items: {seller_id: user.try(:id)}).where(orders: {purchase_state: EffectiveOrders::PURCHASED}).uniq }
|
31
34
|
|
32
35
|
scope :not_purchased, -> { where('id NOT IN (?)', purchased.pluck(:id).presence || [0]) }
|
33
36
|
scope :not_purchased_by, lambda { |user| where('id NOT IN (?)', purchased_by(user).pluck(:id).presence || [0]) }
|
@@ -44,9 +47,6 @@ module ActsAsPurchasable
|
|
44
47
|
end
|
45
48
|
|
46
49
|
# Regular instance methods
|
47
|
-
def is_effectively_purchasable?
|
48
|
-
true
|
49
|
-
end
|
50
50
|
|
51
51
|
def price
|
52
52
|
self[:price] || 0
|
@@ -75,14 +75,6 @@ module ActsAsPurchasable
|
|
75
75
|
self[:tax_exempt] || false
|
76
76
|
end
|
77
77
|
|
78
|
-
def tax_rate
|
79
|
-
@tax_rate ||= (
|
80
|
-
self.instance_exec(self, &EffectiveOrders.tax_rate_method).to_f.tap do |rate|
|
81
|
-
raise ArgumentError.new("expected EffectiveOrders.tax_rate_method to return a value between 0 and 1. Received #{rate}. Please return 0.05 for 5% tax.") if (rate > 1.0 || rate < 0.0)
|
82
|
-
end
|
83
|
-
)
|
84
|
-
end
|
85
|
-
|
86
78
|
def seller
|
87
79
|
if EffectiveOrders.stripe_connect_enabled
|
88
80
|
raise 'acts_as_purchasable object requires the seller be defined to return the User selling this item. This is only a requirement when using StripeConnect.'
|
@@ -5,16 +5,16 @@ module Effective
|
|
5
5
|
belongs_to :user # This is optional. We want to let non-logged-in people have carts too
|
6
6
|
has_many :cart_items, :inverse_of => :cart, :dependent => :delete_all
|
7
7
|
|
8
|
-
structure do
|
9
|
-
|
10
|
-
end
|
8
|
+
# structure do
|
9
|
+
# timestamps
|
10
|
+
# end
|
11
11
|
|
12
12
|
default_scope -> { includes(:cart_items => :purchasable) }
|
13
13
|
|
14
|
-
def add(item, quantity
|
15
|
-
raise 'expecting an acts_as_purchasable object' unless item.
|
14
|
+
def add(item, quantity: 1)
|
15
|
+
raise 'expecting an acts_as_purchasable object' unless item.kind_of?(ActsAsPurchasable)
|
16
16
|
|
17
|
-
existing_item = cart_items.where(:
|
17
|
+
existing_item = cart_items.where(purchasable_id: item.id, purchasable_type: item.class.name).first
|
18
18
|
|
19
19
|
if item.quantity_enabled? && (quantity + (existing_item.quantity rescue 0)) > item.quantity_remaining
|
20
20
|
raise EffectiveOrders::SoldOutException, "#{item.title} is sold out"
|
@@ -22,9 +22,9 @@ module Effective
|
|
22
22
|
end
|
23
23
|
|
24
24
|
if existing_item.present?
|
25
|
-
existing_item.update_attributes(:
|
25
|
+
existing_item.update_attributes(quantity: existing_item.quantity + quantity)
|
26
26
|
else
|
27
|
-
cart_items.create(:
|
27
|
+
cart_items.create(cart: self, purchasable_id: item.id, purchasable_type: item.class.name, quantity: quantity)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
alias_method :add_to_cart, :add
|
@@ -53,13 +53,7 @@ module Effective
|
|
53
53
|
def subtotal
|
54
54
|
cart_items.map { |ci| ci.subtotal }.sum
|
55
55
|
end
|
56
|
+
alias_method :total, :subtotal
|
56
57
|
|
57
|
-
def tax
|
58
|
-
cart_items.map { |ci| ci.tax }.sum
|
59
|
-
end
|
60
|
-
|
61
|
-
def total
|
62
|
-
cart_items.map { |ci| ci.total }.sum
|
63
|
-
end
|
64
58
|
end
|
65
59
|
end
|