effective_orders 3.2.3 → 4.0.0beta1
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 +5 -5
- data/MIT-LICENSE +1 -1
- data/README.md +11 -99
- data/app/assets/stylesheets/effective_orders.scss +1 -0
- data/app/assets/stylesheets/effective_orders/_cart.scss +4 -0
- data/app/assets/stylesheets/effective_orders/_order.scss +6 -2
- data/app/controllers/admin/orders_controller.rb +17 -17
- data/app/controllers/effective/carts_controller.rb +4 -2
- data/app/controllers/effective/orders_controller.rb +50 -105
- data/app/controllers/effective/providers/cheque.rb +1 -1
- data/app/controllers/effective/providers/moneris.rb +17 -23
- data/app/controllers/effective/providers/stripe.rb +2 -37
- data/app/datatables/effective_customers_datatable.rb +1 -5
- data/app/datatables/effective_order_items_datatable.rb +10 -10
- data/app/datatables/effective_orders_datatable.rb +23 -46
- data/app/helpers/effective_carts_helper.rb +5 -25
- data/app/helpers/effective_orders_helper.rb +29 -61
- data/app/helpers/effective_paypal_helper.rb +10 -8
- data/app/helpers/effective_stripe_helper.rb +2 -33
- data/app/models/concerns/acts_as_purchasable.rb +26 -37
- data/app/models/concerns/acts_as_subscribable.rb +1 -1
- data/app/models/effective/cart.rb +4 -5
- data/app/models/effective/customer.rb +1 -4
- data/app/models/effective/order.rb +132 -163
- data/app/models/effective/order_item.rb +2 -21
- data/app/models/{effective → validators/effective}/sold_out_validator.rb +0 -0
- data/app/views/admin/customers/index.html.haml +1 -4
- data/app/views/admin/order_items/index.html.haml +1 -4
- data/app/views/admin/orders/_actions.html.haml +18 -9
- data/app/views/admin/orders/_form.html.haml +12 -14
- data/app/views/admin/orders/_form_note_internal.html.haml +2 -2
- data/app/views/admin/orders/_order_actions.html.haml +8 -5
- data/app/views/admin/orders/_order_item_fields.html.haml +9 -8
- data/app/views/admin/orders/checkout.html.haml +3 -0
- data/app/views/admin/orders/edit.html.haml +3 -1
- data/app/views/admin/orders/new.html.haml +2 -1
- data/app/views/admin/orders/show.html.haml +2 -5
- data/app/views/effective/carts/_cart.html.haml +1 -1
- data/app/views/effective/carts/_cart_actions.html.haml +3 -4
- data/app/views/effective/carts/show.html.haml +4 -4
- data/app/views/effective/orders/_checkout_actions.html.haml +3 -0
- data/app/views/effective/orders/_checkout_step1.html.haml +20 -27
- data/app/views/effective/orders/_checkout_step2.html.haml +26 -30
- data/app/views/effective/orders/_order.html.haml +1 -2
- data/app/views/effective/orders/_order_actions.html.haml +14 -6
- data/app/views/effective/orders/_order_header.html.haml +2 -2
- data/app/views/effective/orders/_order_note_fields.html.haml +6 -4
- data/app/views/effective/orders/_order_notes.html.haml +15 -0
- data/app/views/effective/orders/_order_payment_details.html.haml +1 -1
- data/app/views/effective/orders/_order_shipping.html.haml +6 -6
- data/app/views/effective/orders/_order_terms_and_conditions_fields.html.haml +5 -6
- data/app/views/effective/orders/_order_user_fields.html.haml +5 -11
- data/app/views/effective/orders/_orders_table.html.haml +2 -6
- data/app/views/effective/orders/index.html.haml +2 -1
- data/app/views/effective/orders/mark_as_paid/_form.html.haml +8 -14
- data/app/views/effective/orders/moneris/_form.html.haml +43 -4
- data/app/views/effective/orders/pretend/_form.html.haml +1 -3
- data/app/views/effective/orders/refund/_form.html.haml +8 -12
- data/app/views/effective/orders/show.html.haml +2 -2
- data/app/views/effective/orders/stripe/_form.html.haml +4 -4
- data/app/views/effective/orders_mailer/payment_request_to_buyer.html.haml +1 -1
- data/app/views/effective/orders_mailer/pending_order_invoice_to_buyer.html.haml +1 -1
- data/config/effective_orders.rb +116 -182
- data/config/routes.rb +16 -27
- data/db/migrate/01_create_effective_orders.rb.erb +6 -5
- data/lib/effective_orders.rb +72 -76
- data/lib/effective_orders/engine.rb +8 -80
- data/lib/effective_orders/version.rb +1 -1
- data/lib/generators/templates/effective_orders_mailer_preview.rb +4 -4
- metadata +26 -38
- data/Rakefile +0 -21
- data/app/assets/config/effective_orders_manifest.js +0 -3
- data/app/assets/images/effective_orders/stripe_connect.png +0 -0
- data/app/controllers/effective/providers/app_checkout.rb +0 -38
- data/app/controllers/effective/providers/ccbill.rb +0 -34
- data/app/controllers/effective/providers/stripe_connect.rb +0 -47
- data/app/helpers/effective_ccbill_helper.rb +0 -25
- data/app/models/effective/providers/ccbill_postback.rb +0 -85
- data/app/models/effective/providers/moneris_charge.rb +0 -115
- data/app/views/effective/orders/_order_note.html.haml +0 -5
- data/app/views/effective/orders/_order_note_to_buyer.html.haml +0 -9
- data/app/views/effective/orders/app_checkout/_form.html.haml +0 -2
- data/app/views/effective/orders/ccbill/_form.html.haml +0 -24
- data/app/views/effective/orders/my_purchases.html.haml +0 -3
- data/app/views/effective/orders/my_sales.html.haml +0 -25
- data/app/views/effective/orders_mailer/order_receipt_to_seller.html.haml +0 -25
- data/lib/effective_orders/app_checkout_service.rb +0 -26
@@ -2,22 +2,24 @@ module EffectivePaypalHelper
|
|
2
2
|
class ConfigReader
|
3
3
|
def self.cert_or_key(config)
|
4
4
|
if File.exist?(EffectiveOrders.paypal[config])
|
5
|
-
File.read(EffectiveOrders.paypal[config]) rescue
|
5
|
+
(File.read(EffectiveOrders.paypal[config]) rescue nil)
|
6
6
|
else
|
7
|
-
EffectiveOrders.paypal[config]
|
7
|
+
EffectiveOrders.paypal[config]
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
# These're constants so they only get read once, not every order request
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
if EffectiveOrders.paypal?
|
14
|
+
PAYPAL_CERT_PEM = ConfigReader.cert_or_key(:paypal_cert)
|
15
|
+
APP_CERT_PEM = ConfigReader.cert_or_key(:app_cert)
|
16
|
+
APP_KEY_PEM = ConfigReader.cert_or_key(:app_key)
|
17
|
+
end
|
16
18
|
|
17
19
|
def paypal_encrypted_payload(order)
|
18
|
-
raise
|
19
|
-
raise
|
20
|
-
raise
|
20
|
+
raise 'required paypal paypal_cert is missing' unless PAYPAL_CERT_PEM.present?
|
21
|
+
raise 'required paypal app_cert is missing' unless APP_CERT_PEM.present?
|
22
|
+
raise 'required paypal app_key is missing' unless APP_KEY_PEM.present?
|
21
23
|
|
22
24
|
values = {
|
23
25
|
business: EffectiveOrders.paypal[:seller_email],
|
@@ -1,36 +1,5 @@
|
|
1
1
|
module EffectiveStripeHelper
|
2
2
|
|
3
|
-
STRIPE_CONNECT_AUTHORIZE_URL = 'https://connect.stripe.com/oauth/authorize'
|
4
|
-
STRIPE_CONNECT_TOKEN_URL = 'https://connect.stripe.com/oauth/token'
|
5
|
-
|
6
|
-
def is_stripe_connect_seller?(user)
|
7
|
-
Effective::Customer.for_buyer(user).stripe_connect_access_token.present?
|
8
|
-
end
|
9
|
-
|
10
|
-
def link_to_new_stripe_connect_customer(opts = {})
|
11
|
-
client_id = EffectiveOrders.stripe[:connect_client_id]
|
12
|
-
|
13
|
-
raise 'effective_orders config: stripe.connect_client_id has not been set' unless client_id.present?
|
14
|
-
|
15
|
-
authorize_params = {
|
16
|
-
response_type: :code,
|
17
|
-
client_id: client_id, # This is the Application's ClientID
|
18
|
-
scope: :read_write,
|
19
|
-
state: {
|
20
|
-
form_authenticity_token: form_authenticity_token, # Rails standard CSRF
|
21
|
-
redirect_to: URI.encode(request.original_url) # TODO: Allow this to be customized
|
22
|
-
}.to_json
|
23
|
-
}
|
24
|
-
|
25
|
-
# Add the stripe_user parameter if it's possible
|
26
|
-
stripe_user_params = opts.delete :stripe_user
|
27
|
-
authorize_params.merge!({stripe_user: stripe_user_params}) if stripe_user_params.is_a?(Hash)
|
28
|
-
|
29
|
-
authorize_url = STRIPE_CONNECT_AUTHORIZE_URL + '?' + authorize_params.to_query
|
30
|
-
options = {}.merge(opts)
|
31
|
-
link_to image_tag('/assets/effective_orders/stripe_connect.png'), authorize_url, options
|
32
|
-
end
|
33
|
-
|
34
3
|
def stripe_plan_description(obj)
|
35
4
|
plan = (
|
36
5
|
case obj
|
@@ -58,7 +27,7 @@ module EffectiveStripeHelper
|
|
58
27
|
end
|
59
28
|
|
60
29
|
def stripe_coupon_description(coupon)
|
61
|
-
amount = coupon.amount_off.present? ?
|
30
|
+
amount = coupon.amount_off.present? ? price_to_currency(coupon.amount_off) : "#{coupon.percent_off}%"
|
62
31
|
|
63
32
|
if coupon.duration_in_months.present?
|
64
33
|
"#{coupon.id} - #{amount} off for #{coupon.duration_in_months} months"
|
@@ -68,7 +37,7 @@ module EffectiveStripeHelper
|
|
68
37
|
end
|
69
38
|
|
70
39
|
def stripe_site_image_url
|
71
|
-
return nil unless EffectiveOrders.stripe && (url = EffectiveOrders.stripe[:site_image].to_s).present?
|
40
|
+
return nil unless EffectiveOrders.stripe? && (url = EffectiveOrders.stripe[:site_image].to_s).present?
|
72
41
|
url.start_with?('http') ? url : asset_url(url)
|
73
42
|
end
|
74
43
|
|
@@ -7,36 +7,40 @@ module ActsAsPurchasable
|
|
7
7
|
def acts_as_purchasable(*options)
|
8
8
|
@acts_as_purchasable = options || []
|
9
9
|
include ::ActsAsPurchasable
|
10
|
-
|
10
|
+
|
11
|
+
instance = new()
|
12
|
+
raise 'must respond_to price' unless instance.respond_to?(:price)
|
13
|
+
raise 'must respond_to purchased_order_id' unless instance.respond_to?(:purchased_order_id)
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
14
17
|
included do
|
18
|
+
belongs_to :purchased_order, class_name: 'Effective::Order' # Set when purchased
|
19
|
+
|
15
20
|
has_many :cart_items, as: :purchasable, dependent: :delete_all, class_name: 'Effective::CartItem'
|
16
21
|
|
17
22
|
has_many :order_items, as: :purchasable, class_name: 'Effective::OrderItem'
|
18
23
|
has_many :orders, -> { order(:id) }, through: :order_items, class_name: 'Effective::Order'
|
19
24
|
|
20
|
-
has_many :purchased_orders, -> { where(
|
25
|
+
has_many :purchased_orders, -> { where(state: EffectiveOrders::PURCHASED).order(:purchased_at) },
|
21
26
|
through: :order_items, class_name: 'Effective::Order', source: :order
|
22
27
|
|
23
|
-
validates_with Effective::SoldOutValidator, on: :create
|
24
|
-
|
25
28
|
# Database max integer value is 2147483647. So let's round that down and use a max/min of $20 million (2000000000)
|
26
29
|
validates :price, presence: true, numericality: { less_than_or_equal_to: 2000000000, message: 'maximum price is $20,000,000' }
|
27
|
-
|
28
30
|
validates :tax_exempt, inclusion: { in: [true, false] }
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
32
|
+
with_options(if: -> { quantity_enabled? }) do
|
33
|
+
validates :quantity_purchased, numericality: { allow_nil: true }
|
34
|
+
validates :quantity_max, numericality: { allow_nil: true }
|
35
|
+
validates_with Effective::SoldOutValidator, on: :create
|
36
|
+
end
|
33
37
|
|
34
|
-
scope :purchased, -> {
|
35
|
-
scope :
|
36
|
-
scope :sold, -> { purchased() }
|
37
|
-
scope :sold_by, lambda { |user| joins(order_items: :order).where(order_items: {seller_id: user.try(:id)}).where(orders: {purchase_state: EffectiveOrders::PURCHASED}).distinct }
|
38
|
+
scope :purchased, -> { where.not(purchased_order_id: nil) }
|
39
|
+
scope :not_purchased, -> { where(purchased_order_id: nil) }
|
38
40
|
|
39
|
-
scope :
|
41
|
+
# scope :purchased, -> { joins(order_items: :order).where(orders: {state: EffectiveOrders::PURCHASED}).distinct }
|
42
|
+
# scope :not_purchased, -> { where('id NOT IN (?)', purchased.pluck(:id).presence || [0]) }
|
43
|
+
scope :purchased_by, lambda { |user| joins(order_items: :order).where(orders: { user_id: user.try(:id), state: EffectiveOrders::PURCHASED }).distinct }
|
40
44
|
scope :not_purchased_by, lambda { |user| where('id NOT IN (?)', purchased_by(user).pluck(:id).presence || [0]) }
|
41
45
|
end
|
42
46
|
|
@@ -56,10 +60,6 @@ module ActsAsPurchasable
|
|
56
60
|
|
57
61
|
# Regular instance methods
|
58
62
|
|
59
|
-
def price
|
60
|
-
self[:price] || 0
|
61
|
-
end
|
62
|
-
|
63
63
|
# If I have a column type of Integer, and I'm passed a non-Integer, convert it here
|
64
64
|
def price=(value)
|
65
65
|
if value.kind_of?(Integer)
|
@@ -79,43 +79,32 @@ module ActsAsPurchasable
|
|
79
79
|
self[:tax_exempt] || false
|
80
80
|
end
|
81
81
|
|
82
|
-
def
|
83
|
-
|
84
|
-
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.'
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def purchased_order
|
89
|
-
@purchased_order ||= purchased_orders.first
|
82
|
+
def purchased?
|
83
|
+
purchased_order_id.present?
|
90
84
|
end
|
91
85
|
|
92
|
-
def
|
93
|
-
|
86
|
+
def purchased_at
|
87
|
+
purchased_order.try(:purchased_at)
|
94
88
|
end
|
95
89
|
|
96
90
|
def purchased_by?(user)
|
97
91
|
purchased_orders.any? { |order| order.user_id == user.id }
|
98
92
|
end
|
99
93
|
|
100
|
-
def
|
101
|
-
|
94
|
+
def purchased_download_url # Override me if this is a digital purchase.
|
95
|
+
false
|
102
96
|
end
|
103
97
|
|
104
98
|
def quantity_enabled?
|
105
|
-
|
99
|
+
false
|
106
100
|
end
|
107
101
|
|
108
102
|
def quantity_remaining
|
109
|
-
|
103
|
+
quantity_max - quantity_purchased if quantity_enabled?
|
110
104
|
end
|
111
105
|
|
112
106
|
def sold_out?
|
113
|
-
quantity_enabled? ? (quantity_remaining
|
114
|
-
end
|
115
|
-
|
116
|
-
# Override me if this is a digital purchase.
|
117
|
-
def purchased_download_url
|
118
|
-
false
|
107
|
+
quantity_enabled? ? (quantity_remaining <= 0) : false
|
119
108
|
end
|
120
109
|
|
121
110
|
end
|
@@ -57,7 +57,7 @@ module ActsAsSubscribable
|
|
57
57
|
|
58
58
|
def trial_expires_at
|
59
59
|
# The rake task send_trial_expiring_emails depends on this beginning_of_day
|
60
|
-
((created_at || Time.zone.now) + EffectiveOrders.
|
60
|
+
((created_at || Time.zone.now) + EffectiveOrders.subscriptions[:trial_period]).beginning_of_day
|
61
61
|
end
|
62
62
|
|
63
63
|
def buyer
|
@@ -2,9 +2,8 @@ module Effective
|
|
2
2
|
class Cart < ActiveRecord::Base
|
3
3
|
self.table_name = EffectiveOrders.carts_table_name.to_s
|
4
4
|
|
5
|
-
# Optional. We want non-logged-in users to have carts too.
|
6
|
-
|
7
|
-
has_many :cart_items, -> { includes(:purchasable).order(:updated_at) }, dependent: :delete_all, class_name: 'Effective::CartItem'
|
5
|
+
belongs_to :user # Optional. We want non-logged-in users to have carts too.
|
6
|
+
has_many :cart_items, -> { order(:id) }, dependent: :delete_all, class_name: 'Effective::CartItem'
|
8
7
|
|
9
8
|
accepts_nested_attributes_for :cart_items
|
10
9
|
|
@@ -43,13 +42,13 @@ module Effective
|
|
43
42
|
raise EffectiveOrders::SoldOutException, "#{item.title} is sold out"
|
44
43
|
end
|
45
44
|
|
46
|
-
existing ||= cart_items.build(purchasable: item, quantity: quantity, unique: (unique.to_s
|
45
|
+
existing ||= cart_items.build(purchasable: item, quantity: quantity, unique: (unique.to_s unless unique.kind_of?(Proc)))
|
47
46
|
save!
|
48
47
|
end
|
49
48
|
|
50
49
|
def clear!
|
51
50
|
cart_items.each { |cart_item| cart_item.mark_for_destruction }
|
52
|
-
save!
|
51
|
+
cart_items.present? ? save! : true
|
53
52
|
end
|
54
53
|
|
55
54
|
def remove(item)
|
@@ -2,7 +2,6 @@ module Effective
|
|
2
2
|
class Customer < ActiveRecord::Base
|
3
3
|
self.table_name = EffectiveOrders.customers_table_name.to_s
|
4
4
|
|
5
|
-
attr_accessor :stripe_token # This is a convenience method so we have a place to store StripeConnect temporary access tokens
|
6
5
|
attr_accessor :stripe_customer, :stripe_subscription
|
7
6
|
|
8
7
|
belongs_to :user
|
@@ -18,8 +17,6 @@ module Effective
|
|
18
17
|
# stripe_subscription_id :string # Each user gets one stripe subscription object, which can contain many items
|
19
18
|
# status :string
|
20
19
|
|
21
|
-
# stripe_connect_access_token :string # If using StripeConnect and this user is a connected Seller
|
22
|
-
#
|
23
20
|
# timestamps
|
24
21
|
|
25
22
|
scope :deep, -> { includes(subscriptions: :subscribable) }
|
@@ -32,7 +29,7 @@ module Effective
|
|
32
29
|
validates :stripe_customer_id, presence: true
|
33
30
|
validates :status, if: -> { stripe_subscription_id.present? }, inclusion: { in: %w(active past_due) }
|
34
31
|
|
35
|
-
def self.
|
32
|
+
def self.for_user(user)
|
36
33
|
Effective::Customer.where(user: user).first_or_initialize
|
37
34
|
end
|
38
35
|
|
@@ -1,3 +1,12 @@
|
|
1
|
+
# When an Order is first initialized it is done in the pending state
|
2
|
+
# - when it's in the pending state, none of the buyer entered information is required
|
3
|
+
# - when a pending order is rendered:
|
4
|
+
# - if the user has a billing address, go to step 2
|
5
|
+
# - if the user has no billing address, go to step 1
|
6
|
+
#
|
7
|
+
# After Step1, we go to the confirmed state
|
8
|
+
# After Step2, we are in the purchased or declined state
|
9
|
+
|
1
10
|
module Effective
|
2
11
|
class Order < ActiveRecord::Base
|
3
12
|
self.table_name = EffectiveOrders.orders_table_name.to_s
|
@@ -17,7 +26,6 @@ module Effective
|
|
17
26
|
attr_accessor :send_payment_request_to_buyer # Set by Admin::Orders#new. Should the payment request email be sent after creating an order?
|
18
27
|
attr_accessor :send_mark_as_paid_email_to_buyer # Set by Admin::Orders#mark_as_paid
|
19
28
|
attr_accessor :skip_buyer_validations # Set by Admin::Orders#create
|
20
|
-
attr_accessor :skip_minimum_charge_validation # Set by Admin::Orders#show
|
21
29
|
|
22
30
|
belongs_to :user, validate: false # This is the buyer/user of the order. We validate it below.
|
23
31
|
has_many :order_items, -> { order(:id) }, inverse_of: :order, class_name: 'Effective::OrderItem'
|
@@ -26,13 +34,15 @@ module Effective
|
|
26
34
|
accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
|
27
35
|
|
28
36
|
# Attributes
|
29
|
-
#
|
37
|
+
# state :string
|
30
38
|
# purchased_at :datetime
|
31
39
|
#
|
32
|
-
#
|
33
|
-
#
|
40
|
+
# note :text # From buyer to admin
|
41
|
+
# note_to_buyer :text # From admin to buyer
|
42
|
+
# note_internal :text # Internal admin only
|
34
43
|
#
|
35
|
-
#
|
44
|
+
# billing_name :string # name of buyer
|
45
|
+
# payment :text # serialized hash containing all the payment details.
|
36
46
|
#
|
37
47
|
# payment_provider :string
|
38
48
|
# payment_card :string
|
@@ -47,38 +57,38 @@ module Effective
|
|
47
57
|
|
48
58
|
serialize :payment, Hash
|
49
59
|
|
50
|
-
before_validation {
|
60
|
+
before_validation { assign_order_totals }
|
61
|
+
before_validation { assign_billing_name }
|
62
|
+
before_validation { assign_last_address }
|
51
63
|
|
52
64
|
# Order validations
|
65
|
+
validates :user_id, presence: true
|
53
66
|
validates :order_items, presence: { message: 'No items are present. Please add additional items.' }
|
54
|
-
validates :
|
67
|
+
validates :state, inclusion: { in: EffectiveOrders::STATES.keys }
|
55
68
|
validates :subtotal, presence: true
|
56
69
|
|
57
70
|
if EffectiveOrders.minimum_charge.to_i > 0
|
58
71
|
validates :total, presence: true, numericality: {
|
59
72
|
greater_than_or_equal_to: EffectiveOrders.minimum_charge.to_i,
|
60
73
|
message: "must be $#{'%0.2f' % (EffectiveOrders.minimum_charge.to_i / 100.0)} or more. Please add additional items."
|
61
|
-
}, unless: -> {
|
62
|
-
(total == 0 && EffectiveOrders.allow_free_orders) ||
|
63
|
-
(total < 0 && EffectiveOrders.allow_refunds && skip_minimum_charge_validation?)
|
64
|
-
}
|
74
|
+
}, unless: -> { (free? && EffectiveOrders.free?) || (refund? && EffectiveOrders.refunds?) }
|
65
75
|
end
|
66
76
|
|
67
77
|
# User validations -- An admin skips these when working in the admin/ namespace
|
68
|
-
with_options unless: -> { skip_buyer_validations? } do |order|
|
78
|
+
with_options unless: -> { pending? || skip_buyer_validations? } do |order|
|
69
79
|
order.validates :tax_rate, presence: { message: "can't be determined based on billing address" }
|
70
80
|
order.validates :tax, presence: true
|
71
81
|
|
72
82
|
unless EffectiveOrders.skip_user_validation
|
73
|
-
order.validates :
|
83
|
+
order.validates :billing_name, presence: true
|
74
84
|
order.validates :user, associated: true
|
75
85
|
end
|
76
86
|
|
77
|
-
if EffectiveOrders.
|
87
|
+
if EffectiveOrders.billing_address
|
78
88
|
order.validates :billing_address, presence: true
|
79
89
|
end
|
80
90
|
|
81
|
-
if EffectiveOrders.
|
91
|
+
if EffectiveOrders.shipping_address
|
82
92
|
order.validates :shipping_address, presence: true
|
83
93
|
end
|
84
94
|
|
@@ -92,25 +102,20 @@ module Effective
|
|
92
102
|
order.validates :purchased_at, presence: true
|
93
103
|
order.validates :payment, presence: true
|
94
104
|
|
95
|
-
order.validates :payment_provider, presence: true, inclusion: { in: EffectiveOrders.payment_providers
|
105
|
+
order.validates :payment_provider, presence: true, inclusion: { in: EffectiveOrders.payment_providers }
|
96
106
|
order.validates :payment_card, presence: true
|
97
107
|
end
|
98
108
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
scope :deep, -> { includes(:user).includes(order_items: :purchasable) }
|
103
|
-
scope :sorted, -> { order(created_at: :desc) }
|
109
|
+
scope :deep, -> { includes(:user, order_items: :purchasable) }
|
110
|
+
scope :sorted, -> { order(:id) }
|
104
111
|
|
105
|
-
scope :purchased, -> {
|
106
|
-
scope :purchased_by, lambda { |user| purchased.where(
|
107
|
-
scope :
|
108
|
-
scope :pending, -> { where(purchase_state: EffectiveOrders::PENDING) }
|
112
|
+
scope :purchased, -> { where(state: EffectiveOrders::PURCHASED) }
|
113
|
+
scope :purchased_by, lambda { |user| purchased.where(user: user) }
|
114
|
+
scope :not_purchased, -> { where.not(state: EffectiveOrders::PURCHASED) }
|
109
115
|
|
110
|
-
scope :
|
111
|
-
|
112
|
-
|
113
|
-
}
|
116
|
+
scope :pending, -> { where(state: EffectiveOrders::PENDING) }
|
117
|
+
scope :confirmed, -> { where(state: EffectiveOrders::CONFIRMED) }
|
118
|
+
scope :declined, -> { where(state: EffectiveOrders::DECLINED) }
|
114
119
|
|
115
120
|
# Effective::Order.new()
|
116
121
|
# Effective::Order.new(Product.first)
|
@@ -122,27 +127,29 @@ module Effective
|
|
122
127
|
# Effective::Order.new(items: Product.first, user: User.first, billing_address: Effective::Address.new, shipping_address: Effective::Address.new)
|
123
128
|
|
124
129
|
def initialize(atts = nil, &block)
|
125
|
-
super(
|
130
|
+
super(state: EffectiveOrders::PENDING) # Initialize with state: PENDING
|
126
131
|
|
127
132
|
return unless atts.present?
|
128
133
|
|
129
134
|
if atts.kind_of?(Hash)
|
130
|
-
|
135
|
+
if (keywords = (atts.keys - [:item, :items, :user, :billing_address, :shipping_address])).present?
|
136
|
+
raise ArgumentError.new("unknown keyword: #{keywords.join(' ')}")
|
137
|
+
end
|
138
|
+
|
139
|
+
items = Array(atts[:item]) + Array(atts[:items])
|
131
140
|
|
132
|
-
self.user = atts
|
141
|
+
self.user = atts[:user] || (items.first.user if items.first.respond_to?(:user))
|
133
142
|
|
134
|
-
if
|
135
|
-
self.billing_address =
|
143
|
+
if atts.key?(:billing_address)
|
144
|
+
self.billing_address = atts[:billing_address]
|
136
145
|
self.billing_address.full_name ||= user.to_s.presence
|
137
146
|
end
|
138
147
|
|
139
|
-
if
|
140
|
-
self.shipping_address =
|
148
|
+
if atts.key?(:shipping_address)
|
149
|
+
self.shipping_address = atts[:shipping_address]
|
141
150
|
self.shipping_address.full_name ||= user.to_s.presence
|
142
151
|
end
|
143
152
|
|
144
|
-
atts.each { |key, value| self.send("#{key}=", value) }
|
145
|
-
|
146
153
|
add(items) if items.present?
|
147
154
|
else # Attributes are not a Hash
|
148
155
|
self.user = atts.user if atts.respond_to?(:user)
|
@@ -150,6 +157,7 @@ module Effective
|
|
150
157
|
end
|
151
158
|
end
|
152
159
|
|
160
|
+
# Items can be an Effective::Cart, an Effective::order, a single acts_as_purchasable, or multiple acts_as_purchasables
|
153
161
|
# add(Product.first) => returns an Effective::OrderItem
|
154
162
|
# add(Product.first, current_cart) => returns an array of Effective::OrderItems
|
155
163
|
def add(*items, quantity: 1)
|
@@ -192,41 +200,6 @@ module Effective
|
|
192
200
|
retval.size == 1 ? retval.first : retval
|
193
201
|
end
|
194
202
|
|
195
|
-
def user=(user)
|
196
|
-
super
|
197
|
-
|
198
|
-
return unless user.present?
|
199
|
-
|
200
|
-
# Copy user addresses into this order if they are present
|
201
|
-
if user.respond_to?(:billing_address) && user.billing_address.present?
|
202
|
-
self.billing_address = user.billing_address
|
203
|
-
end
|
204
|
-
|
205
|
-
if user.respond_to?(:shipping_address) && user.shipping_address.present?
|
206
|
-
self.shipping_address = user.shipping_address
|
207
|
-
end
|
208
|
-
|
209
|
-
user
|
210
|
-
end
|
211
|
-
|
212
|
-
# This is called from admin/orders#create
|
213
|
-
# This is intended for use as an admin action only
|
214
|
-
# It skips any address or bad user validations
|
215
|
-
def create_as_pending!
|
216
|
-
return false unless new_record?
|
217
|
-
|
218
|
-
self.purchase_state = EffectiveOrders::PENDING
|
219
|
-
|
220
|
-
self.skip_buyer_validations = true
|
221
|
-
self.addresses.clear if addresses.any? { |address| address.valid? == false }
|
222
|
-
|
223
|
-
save!
|
224
|
-
|
225
|
-
send_payment_request_to_buyer! if send_payment_request_to_buyer?
|
226
|
-
|
227
|
-
true
|
228
|
-
end
|
229
|
-
|
230
203
|
def to_s
|
231
204
|
if refund?
|
232
205
|
"Refund ##{to_param}"
|
@@ -239,18 +212,32 @@ module Effective
|
|
239
212
|
end
|
240
213
|
end
|
241
214
|
|
242
|
-
def
|
243
|
-
|
215
|
+
def pending?
|
216
|
+
state == EffectiveOrders::PENDING
|
244
217
|
end
|
245
218
|
|
246
|
-
def
|
247
|
-
|
219
|
+
def confirmed?
|
220
|
+
state == EffectiveOrders::CONFIRMED
|
221
|
+
end
|
222
|
+
|
223
|
+
def purchased?(provider = nil)
|
224
|
+
return false if (state != EffectiveOrders::PURCHASED)
|
225
|
+
return true if provider.nil? || payment_provider == provider.to_s
|
226
|
+
false
|
227
|
+
end
|
228
|
+
|
229
|
+
def declined?
|
230
|
+
state == EffectiveOrders::DECLINED
|
248
231
|
end
|
249
232
|
|
250
233
|
def purchasables
|
251
234
|
order_items.map { |order_item| order_item.purchasable }
|
252
235
|
end
|
253
236
|
|
237
|
+
def subtotal
|
238
|
+
self[:subtotal] || order_items.map { |oi| oi.subtotal }.sum
|
239
|
+
end
|
240
|
+
|
254
241
|
def tax_rate
|
255
242
|
self[:tax_rate] || get_tax_rate()
|
256
243
|
end
|
@@ -259,20 +246,24 @@ module Effective
|
|
259
246
|
self[:tax] || get_tax()
|
260
247
|
end
|
261
248
|
|
262
|
-
def subtotal
|
263
|
-
self[:subtotal] || order_items.map { |oi| oi.subtotal }.sum
|
264
|
-
end
|
265
|
-
|
266
249
|
def total
|
267
250
|
(self[:total] || (subtotal + tax.to_i)).to_i
|
268
251
|
end
|
269
252
|
|
253
|
+
def free?
|
254
|
+
total == 0
|
255
|
+
end
|
256
|
+
|
257
|
+
def refund?
|
258
|
+
total.to_i < 0
|
259
|
+
end
|
260
|
+
|
270
261
|
def num_items
|
271
262
|
order_items.map { |oi| oi.quantity }.sum
|
272
263
|
end
|
273
264
|
|
274
265
|
def send_payment_request_to_buyer?
|
275
|
-
truthy?(send_payment_request_to_buyer)
|
266
|
+
truthy?(send_payment_request_to_buyer) && !free? && !refund?
|
276
267
|
end
|
277
268
|
|
278
269
|
def send_mark_as_paid_email_to_buyer?
|
@@ -283,18 +274,34 @@ module Effective
|
|
283
274
|
truthy?(skip_buyer_validations)
|
284
275
|
end
|
285
276
|
|
286
|
-
|
287
|
-
|
277
|
+
# This is called from admin/orders#create
|
278
|
+
# This is intended for use as an admin action only
|
279
|
+
# It skips any address or bad user validations
|
280
|
+
# It's basically the same as save! on a new order, except it might send the payment request to buyer
|
281
|
+
def pending!
|
282
|
+
self.state = EffectiveOrders::PENDING
|
283
|
+
self.addresses.clear if addresses.any? { |address| address.valid? == false }
|
284
|
+
save!
|
285
|
+
|
286
|
+
send_payment_request_to_buyer! if send_payment_request_to_buyer?
|
287
|
+
true
|
288
288
|
end
|
289
289
|
|
290
|
-
def
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
290
|
+
def confirm!
|
291
|
+
self.state = EffectiveOrders::CONFIRMED
|
292
|
+
save!
|
293
|
+
end
|
294
|
+
|
295
|
+
# This lets us skip to the confirmed workflow for an admin...
|
296
|
+
def assign_confirmed_if_valid!
|
297
|
+
return unless pending?
|
298
|
+
|
299
|
+
self.state = EffectiveOrders::CONFIRMED
|
300
|
+
return true if valid?
|
301
|
+
|
302
|
+
self.errors.clear
|
303
|
+
self.state = EffectiveOrders::PENDING
|
304
|
+
false
|
298
305
|
end
|
299
306
|
|
300
307
|
# Effective::Order.new(Product.first, user: User.first).purchase!(details: 'manual purchase')
|
@@ -307,31 +314,24 @@ module Effective
|
|
307
314
|
|
308
315
|
Effective::Order.transaction do
|
309
316
|
begin
|
310
|
-
self.
|
317
|
+
self.state = EffectiveOrders::PURCHASED
|
311
318
|
self.purchased_at ||= Time.zone.now
|
312
319
|
|
313
|
-
self.payment = (
|
314
|
-
if details.kind_of?(Hash)
|
315
|
-
details
|
316
|
-
elsif details.respond_to?(:to_unsafe_h)
|
317
|
-
details.to_unsafe_h
|
318
|
-
else
|
319
|
-
{ details: details.to_s }
|
320
|
-
end
|
321
|
-
)
|
322
|
-
|
320
|
+
self.payment = details.kind_of?(Hash) ? details : { details: details.to_s }
|
323
321
|
self.payment_provider = provider.to_s
|
324
322
|
self.payment_card = card.to_s.presence || 'none'
|
325
323
|
|
326
324
|
self.skip_buyer_validations = skip_buyer_validations
|
327
325
|
|
326
|
+
assign_purchased_order_to_purchasables
|
327
|
+
|
328
328
|
run_purchasable_callbacks(:before_purchase)
|
329
329
|
|
330
330
|
save!(validate: validate)
|
331
331
|
|
332
332
|
success = true
|
333
333
|
rescue => e
|
334
|
-
self.
|
334
|
+
self.state = state_was
|
335
335
|
self.purchased_at = purchased_at_was
|
336
336
|
|
337
337
|
error = e.message
|
@@ -339,7 +339,7 @@ module Effective
|
|
339
339
|
end
|
340
340
|
end
|
341
341
|
|
342
|
-
raise "Failed to purchase
|
342
|
+
raise "Failed to purchase order: #{error || errors.full_messages.to_sentence}" unless success
|
343
343
|
|
344
344
|
send_order_receipts! if email
|
345
345
|
|
@@ -358,7 +358,7 @@ module Effective
|
|
358
358
|
|
359
359
|
Effective::Order.transaction do
|
360
360
|
begin
|
361
|
-
self.
|
361
|
+
self.state = EffectiveOrders::DECLINED
|
362
362
|
self.purchased_at = nil
|
363
363
|
|
364
364
|
self.payment = details.kind_of?(Hash) ? details : { details: details.to_s }
|
@@ -369,7 +369,7 @@ module Effective
|
|
369
369
|
|
370
370
|
success = true
|
371
371
|
rescue => e
|
372
|
-
self.
|
372
|
+
self.state = state_was
|
373
373
|
self.purchased_at = purchased_at_was
|
374
374
|
|
375
375
|
error = e.message
|
@@ -377,38 +377,16 @@ module Effective
|
|
377
377
|
end
|
378
378
|
end
|
379
379
|
|
380
|
-
raise "Failed to decline
|
380
|
+
raise "Failed to decline order: #{error || errors.full_messages.to_sentence}" unless success
|
381
381
|
|
382
382
|
run_purchasable_callbacks(:after_decline)
|
383
383
|
|
384
384
|
true
|
385
385
|
end
|
386
386
|
|
387
|
-
def abandoned?
|
388
|
-
purchase_state == EffectiveOrders::ABANDONED
|
389
|
-
end
|
390
|
-
|
391
|
-
def purchased?(provider = nil)
|
392
|
-
return false if (purchase_state != EffectiveOrders::PURCHASED)
|
393
|
-
return true if provider.nil? || payment_provider == provider.to_s
|
394
|
-
|
395
|
-
unless EffectiveOrder.payment_providers.include?(provider.to_s)
|
396
|
-
raise "Unknown provider #{provider}. Known providers are #{EffectiveOrders.payment_providers}"
|
397
|
-
end
|
398
|
-
end
|
399
|
-
|
400
|
-
def declined?
|
401
|
-
purchase_state == EffectiveOrders::DECLINED
|
402
|
-
end
|
403
|
-
|
404
|
-
def pending?
|
405
|
-
purchase_state == EffectiveOrders::PENDING
|
406
|
-
end
|
407
|
-
|
408
387
|
def send_order_receipts!
|
409
388
|
send_order_receipt_to_admin! if EffectiveOrders.mailer[:send_order_receipt_to_admin]
|
410
389
|
send_order_receipt_to_buyer! if EffectiveOrders.mailer[:send_order_receipt_to_buyer]
|
411
|
-
send_order_receipt_to_seller! if EffectiveOrders.mailer[:send_order_receipt_to_seller]
|
412
390
|
end
|
413
391
|
|
414
392
|
def send_order_receipt_to_admin!
|
@@ -419,14 +397,6 @@ module Effective
|
|
419
397
|
send_email(:order_receipt_to_buyer, to_param) if purchased?
|
420
398
|
end
|
421
399
|
|
422
|
-
def send_order_receipt_to_seller!
|
423
|
-
return false unless (EffectiveOrders.stripe_connect_enabled && purchased?(:stripe_connect))
|
424
|
-
|
425
|
-
order_items.group_by { |oi| oi.seller }.each do |seller, order_items|
|
426
|
-
send_email(:order_receipt_to_seller, to_param, seller, order_items)
|
427
|
-
end
|
428
|
-
end
|
429
|
-
|
430
400
|
def send_payment_request_to_buyer!
|
431
401
|
send_email(:payment_request_to_buyer, to_param) unless purchased?
|
432
402
|
end
|
@@ -453,46 +423,45 @@ module Effective
|
|
453
423
|
|
454
424
|
private
|
455
425
|
|
456
|
-
def
|
426
|
+
def assign_order_totals
|
457
427
|
self.subtotal = order_items.map { |oi| oi.subtotal }.sum
|
458
428
|
self.tax_rate = get_tax_rate()
|
459
429
|
self.tax = get_tax()
|
460
430
|
self.total = subtotal + (tax || 0)
|
461
431
|
end
|
462
432
|
|
463
|
-
def
|
464
|
-
|
465
|
-
|
466
|
-
|
433
|
+
def assign_billing_name
|
434
|
+
self.billing_name = [(billing_address.full_name.presence if billing_address.present?), (user.to_s.presence)].compact.first
|
435
|
+
end
|
436
|
+
|
437
|
+
def assign_last_address
|
438
|
+
return unless user.present?
|
439
|
+
return unless (EffectiveOrders.billing_address || EffectiveOrders.shipping_address)
|
440
|
+
return if EffectiveOrders.billing_address && billing_address.present?
|
441
|
+
return if EffectiveOrders.shipping_address && shipping_address.present?
|
442
|
+
|
443
|
+
last_order = Effective::Order.sorted.where(user: user).last
|
444
|
+
return unless last_order.present?
|
445
|
+
|
446
|
+
if EffectiveOrders.billing_address && last_order.billing_address.present?
|
447
|
+
self.billing_address = last_order.billing_address
|
467
448
|
end
|
468
449
|
|
469
|
-
if
|
470
|
-
|
471
|
-
user.shipping_address.save
|
450
|
+
if EffectiveOrders.shipping_address && last_order.shipping_address.present?
|
451
|
+
self.shipping_address = last_order.shipping_address
|
472
452
|
end
|
473
453
|
end
|
474
454
|
|
455
|
+
def assign_purchased_order_to_purchasables
|
456
|
+
order_items.each { |oi| oi.purchasable.purchased_order = self }
|
457
|
+
end
|
458
|
+
|
475
459
|
def run_purchasable_callbacks(name)
|
476
|
-
|
477
|
-
order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
|
478
|
-
rescue => e
|
479
|
-
raise e unless Rails.env.production?
|
480
|
-
end
|
460
|
+
order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
|
481
461
|
end
|
482
462
|
|
483
463
|
def send_email(email, *mailer_args)
|
484
|
-
|
485
|
-
if EffectiveOrders.mailer[:delayed_job_deliver] && EffectiveOrders.mailer[:deliver_method] == :deliver_later
|
486
|
-
Effective::OrdersMailer.delay.public_send(email, *mailer_args)
|
487
|
-
elsif EffectiveOrders.mailer[:deliver_method].present?
|
488
|
-
Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
|
489
|
-
else
|
490
|
-
Effective::OrdersMailer.public_send(email, *mailer_args).deliver_now
|
491
|
-
end
|
492
|
-
rescue => e
|
493
|
-
raise e unless Rails.env.production?
|
494
|
-
return false
|
495
|
-
end
|
464
|
+
Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
|
496
465
|
end
|
497
466
|
|
498
467
|
def truthy?(value)
|