tb_checkout 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +162 -0
- data/Rakefile +24 -0
- data/app/assets/images/tb_checkout/admin/shopping_carts.png +0 -0
- data/app/assets/images/tb_checkout/admin/transactions.png +0 -0
- data/app/assets/javascripts/tb_checkout/admin/carts.js +2 -0
- data/app/assets/javascripts/tb_checkout/admin/transactions.js +2 -0
- data/app/assets/javascripts/tb_checkout/application.js +13 -0
- data/app/assets/javascripts/tb_checkout/cart_items.js +2 -0
- data/app/assets/javascripts/tb_checkout/carts.js +2 -0
- data/app/assets/javascripts/tb_checkout/transactions.js +2 -0
- data/app/assets/stylesheets/tb_checkout/admin/carts.css +4 -0
- data/app/assets/stylesheets/tb_checkout/admin/transactions.css +4 -0
- data/app/assets/stylesheets/tb_checkout/application.css +15 -0
- data/app/assets/stylesheets/tb_checkout/cart_items.css +4 -0
- data/app/assets/stylesheets/tb_checkout/carts.css +4 -0
- data/app/assets/stylesheets/tb_checkout/transactions.css +4 -0
- data/app/controllers/concerns/tb_checkout/controller_helpers.rb +16 -0
- data/app/controllers/tb_checkout/admin/carts_controller.rb +39 -0
- data/app/controllers/tb_checkout/admin/transactions_controller.rb +26 -0
- data/app/controllers/tb_checkout/application_controller.rb +6 -0
- data/app/controllers/tb_checkout/cart_items_controller.rb +63 -0
- data/app/controllers/tb_checkout/carts_controller.rb +8 -0
- data/app/controllers/tb_checkout/transactions_controller.rb +87 -0
- data/app/helpers/tb_checkout/admin/carts_helper.rb +13 -0
- data/app/helpers/tb_checkout/admin/transactions_helper.rb +16 -0
- data/app/helpers/tb_checkout/application_helper.rb +138 -0
- data/app/helpers/tb_checkout/transactions_helper.rb +2 -0
- data/app/models/concerns/tb_checkout/belongs_to_user_session.rb +15 -0
- data/app/models/concerns/tb_checkout/purchasable.rb +81 -0
- data/app/models/tb_checkout/basic_product.rb +6 -0
- data/app/models/tb_checkout/cart.rb +56 -0
- data/app/models/tb_checkout/cart_item.rb +48 -0
- data/app/models/tb_checkout/transaction.rb +253 -0
- data/app/views/tb_checkout/admin/carts/index.html.erb +54 -0
- data/app/views/tb_checkout/admin/carts/show.html.erb +45 -0
- data/app/views/tb_checkout/admin/transactions/_table.html.erb +24 -0
- data/app/views/tb_checkout/admin/transactions/index.html.erb +7 -0
- data/app/views/tb_checkout/admin/transactions/show.html.erb +70 -0
- data/app/views/tb_checkout/cart_items/create.html.erb +2 -0
- data/app/views/tb_checkout/cart_items/destroy.html.erb +2 -0
- data/app/views/tb_checkout/cart_items/update.html.erb +2 -0
- data/app/views/tb_checkout/carts/_cart.html.erb +65 -0
- data/app/views/tb_checkout/carts/show.html.erb +10 -0
- data/app/views/tb_checkout/transactions/_details.html.erb +17 -0
- data/app/views/tb_checkout/transactions/confirm.html.erb +11 -0
- data/app/views/tb_checkout/transactions/index.html.erb +29 -0
- data/app/views/tb_checkout/transactions/new.html.erb +90 -0
- data/app/views/tb_checkout/transactions/show.html.erb +14 -0
- data/config/locales/en.yml +23 -0
- data/config/routes.rb +20 -0
- data/db/migrate/20140703185256_create_tb_checkout_carts.rb +12 -0
- data/db/migrate/20140703185258_create_tb_checkout_cart_items.rb +15 -0
- data/db/migrate/20140703185259_create_tb_checkout_transactions.rb +23 -0
- data/db/migrate/20140707142350_create_tb_checkout_basic_products.rb +8 -0
- data/db/migrate/20140915202848_add_spud_user_and_session_id_to_transactions.rb +10 -0
- data/lib/tasks/tb_checkout_tasks.rake +19 -0
- data/lib/tb_checkout.rb +4 -0
- data/lib/tb_checkout/configuration.rb +8 -0
- data/lib/tb_checkout/engine.rb +36 -0
- data/lib/tb_checkout/schema.rb +51 -0
- data/lib/tb_checkout/version.rb +3 -0
- data/spec/controllers/tb_checkout/carts_controller_spec.rb +11 -0
- data/spec/controllers/tb_checkout/transactions_controller_spec.rb +110 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +13 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +82 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/migrate/20140915202259_create_spud_admin_permissions.tb_core.rb +12 -0
- data/spec/dummy/db/migrate/20140915202260_create_spud_users.tb_core.rb +30 -0
- data/spec/dummy/db/migrate/20140915202261_add_time_zone_to_spud_user.tb_core.rb +7 -0
- data/spec/dummy/db/migrate/20140915202262_add_scope_to_spud_admin_permissions.tb_core.rb +7 -0
- data/spec/dummy/db/migrate/20140915202263_create_spud_user_settings.tb_core.rb +12 -0
- data/spec/dummy/db/migrate/20140915202264_create_spud_roles.tb_core.rb +11 -0
- data/spec/dummy/db/migrate/20140915202265_create_spud_permissions.tb_core.rb +11 -0
- data/spec/dummy/db/migrate/20140915202266_create_spud_role_permissions.tb_core.rb +12 -0
- data/spec/dummy/db/migrate/20140915202267_drop_spud_admin_permissions.tb_core.rb +16 -0
- data/spec/dummy/db/migrate/20140915202268_create_tb_checkout_carts.tb_checkout_engine.rb +13 -0
- data/spec/dummy/db/migrate/20140915202269_create_tb_checkout_cart_items.tb_checkout_engine.rb +16 -0
- data/spec/dummy/db/migrate/20140915202270_create_tb_checkout_transactions.tb_checkout_engine.rb +24 -0
- data/spec/dummy/db/migrate/20140915202271_create_tb_checkout_basic_products.tb_checkout_engine.rb +9 -0
- data/spec/dummy/db/migrate/20140915234534_add_spud_user_and_session_id_to_transactions.tb_checkout_engine.rb +11 -0
- data/spec/dummy/db/schema.rb +135 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/spud_users.rb +16 -0
- data/spec/factories/tb_checkout_basic_products.rb +8 -0
- data/spec/factories/tb_checkout_cart_items.rb +11 -0
- data/spec/factories/tb_checkout_carts.rb +14 -0
- data/spec/factories/tb_checkout_transactions.rb +37 -0
- data/spec/models/tb_checkout/transaction_spec.rb +5 -0
- data/spec/rails_helper.rb +73 -0
- data/spec/spec_helper.rb +85 -0
- metadata +335 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
class TbCheckout::TransactionsController < TbCheckout::ApplicationController
|
2
|
+
|
3
|
+
before_action :load_cart, :only => [:new, :create, :confirm, :capture]
|
4
|
+
|
5
|
+
def index
|
6
|
+
@transactions = TbCheckout::Transaction.captured.for_user_or_session(current_user_id, session.id).includes(:cart => :cart_items).paginate(:page => params[:page], :per_page => 10)
|
7
|
+
respond_with @transactions
|
8
|
+
end
|
9
|
+
|
10
|
+
def new
|
11
|
+
@transaction = @cart.transactions.new()
|
12
|
+
if !Rails.env.production?
|
13
|
+
# These values will work against the Authorize.Net sandbox environment
|
14
|
+
@transaction.card_type = 'visa'
|
15
|
+
@transaction.card_number = 4007000000027
|
16
|
+
@transaction.card_ccv = 123
|
17
|
+
@transaction.card_expiration = Date.today.next_year
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create
|
22
|
+
@transaction = @cart.transactions.new(transaction_params)
|
23
|
+
if @transaction.save() && @transaction.authorize!
|
24
|
+
redirect_to confirm_tb_checkout_transaction_path(@transaction)
|
25
|
+
else
|
26
|
+
if @transaction.response
|
27
|
+
flash.now[:error] = @transaction.response.message
|
28
|
+
end
|
29
|
+
if @transaction.status == TbCheckout::Transaction::Status::FAIL
|
30
|
+
@transaction = TbCheckout::Transaction.new(transaction_params)
|
31
|
+
end
|
32
|
+
render 'new'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def confirm
|
37
|
+
@transaction = @cart.transactions.authorized.where(:id => params[:id]).first
|
38
|
+
if @transaction.blank?
|
39
|
+
redirect_to new_tb_checkout_transaction_path
|
40
|
+
else
|
41
|
+
render 'confirm'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def capture
|
46
|
+
@transaction = @cart.transactions.authorized.where(:id => params[:id]).first
|
47
|
+
if @transaction.blank?
|
48
|
+
redirect_to new_tb_checkout_transaction_path
|
49
|
+
return false
|
50
|
+
else
|
51
|
+
if @transaction.capture!
|
52
|
+
flash[:notice] = 'Thanks! Your payment was collected successfully'
|
53
|
+
redirect_to tb_checkout_transaction_path(@transaction)
|
54
|
+
else
|
55
|
+
flash.now[:error] = @transaction.response.message if @transaction.response
|
56
|
+
render 'confirm'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def show
|
62
|
+
@transaction = TbCheckout::Transaction.captured.find_by(:id => params[:id])
|
63
|
+
if @transaction.present? && @transaction.belongs_to?(user_id:current_user_id, session_id:session.id)
|
64
|
+
render 'show'
|
65
|
+
else
|
66
|
+
flash[:error] = 'The transaction you were looking for could not be found'
|
67
|
+
redirect_to tb_checkout_cart_path
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def transaction_params
|
74
|
+
params.require(:tb_checkout_transaction).permit(:card_type, :card_number, :card_ccv, :card_expiration,
|
75
|
+
:billing_first_name, :billing_last_name, :billing_address_1, :billing_address_2, :billing_city, :billing_state, :billing_postal)
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_cart
|
79
|
+
@cart = tb_checkout_current_cart
|
80
|
+
if @cart.blank? || @cart.is_empty?
|
81
|
+
flash[:error] = 'You cannot check out with an empty cart'
|
82
|
+
redirect_to tb_checkout_cart_path
|
83
|
+
return false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module TbCheckout::Admin::CartsHelper
|
2
|
+
|
3
|
+
def tb_checkout_status_label_for_cart(cart)
|
4
|
+
if cart.is_completed?
|
5
|
+
content_tag :span, 'Complete', :class => 'label label-success'
|
6
|
+
elsif cart.is_abandoned?
|
7
|
+
content_tag :span, 'Abandoned', :class => 'label label-inverse'
|
8
|
+
else
|
9
|
+
content_tag :span, 'In Progress', :class => 'label label-warning'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module TbCheckout::Admin::TransactionsHelper
|
2
|
+
|
3
|
+
def tb_checkout_status_label_for_transaction(status)
|
4
|
+
if status == 'captured'
|
5
|
+
cls = 'success'
|
6
|
+
elsif status == 'fail'
|
7
|
+
cls = 'important'
|
8
|
+
elsif status == 'refunded' || status == 'voided'
|
9
|
+
cls = 'warning'
|
10
|
+
else
|
11
|
+
cls = 'default'
|
12
|
+
end
|
13
|
+
content_tag :span, status.titleize, :class => "label label-#{cls}"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module TbCheckout
|
2
|
+
module ApplicationHelper
|
3
|
+
|
4
|
+
# Return a link to the user's shopping cart, with the number of items in the text
|
5
|
+
#
|
6
|
+
# * link_text: The text of the link. Defaults: "My Cart"
|
7
|
+
#
|
8
|
+
def tb_checkout_cart_link(link_text:'My Cart')
|
9
|
+
if tb_checkout_current_cart.present?
|
10
|
+
count = tb_checkout_current_cart.cart_items.count
|
11
|
+
else
|
12
|
+
count = 0
|
13
|
+
end
|
14
|
+
link_to "#{link_text} (#{count})", tb_checkout_cart_path, :class => 'tb-checkout-cart-link'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return a form used to add a product to the user's shopping cart
|
18
|
+
#
|
19
|
+
# * product: The product you wish to add. Should be an ActiveRecord model that conforms to TbCheckout::Purchasable
|
20
|
+
# * quantity: Whether to display a quantity picker. Defaults to false
|
21
|
+
# * submit_text: Text of the submit button. Default: "Add to Cart"
|
22
|
+
# * submit_class: CSS class of the submit button. Default: "btn btn-primary"
|
23
|
+
#
|
24
|
+
def tb_checkout_add_to_cart(product, quantity: false, submit_text:'Add to Cart', submit_class:'btn btn-primary')
|
25
|
+
cart_item = TbCheckout::CartItem.new({
|
26
|
+
:item => product,
|
27
|
+
:quantity => 1
|
28
|
+
})
|
29
|
+
return form_for cart_item, :html => {:class => 'tb-checkout-add-to-cart-form'} do |f|
|
30
|
+
concat f.hidden_field :item_type
|
31
|
+
concat f.hidden_field :item_id
|
32
|
+
if quantity
|
33
|
+
concat f.number_field :quantity, {:class => 'tb-checkout-add-to-cart-quantity'}
|
34
|
+
else
|
35
|
+
concat f.hidden_field :quantity
|
36
|
+
end
|
37
|
+
concat f.submit submit_text, :class => "#{submit_class} tb-checkout-add-to-cart-submit"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return a list of states for use in a form select field
|
42
|
+
#
|
43
|
+
def tb_checkout_options_for_states()
|
44
|
+
us_states = [
|
45
|
+
['Alabama', 'AL'],
|
46
|
+
['Alaska', 'AK'],
|
47
|
+
['Arizona', 'AZ'],
|
48
|
+
['Arkansas', 'AR'],
|
49
|
+
['California', 'CA'],
|
50
|
+
['Colorado', 'CO'],
|
51
|
+
['Connecticut', 'CT'],
|
52
|
+
['Delaware', 'DE'],
|
53
|
+
['District of Columbia', 'DC'],
|
54
|
+
['Florida', 'FL'],
|
55
|
+
['Georgia', 'GA'],
|
56
|
+
['Hawaii', 'HI'],
|
57
|
+
['Idaho', 'ID'],
|
58
|
+
['Illinois', 'IL'],
|
59
|
+
['Indiana', 'IN'],
|
60
|
+
['Iowa', 'IA'],
|
61
|
+
['Kansas', 'KS'],
|
62
|
+
['Kentucky', 'KY'],
|
63
|
+
['Louisiana', 'LA'],
|
64
|
+
['Maine', 'ME'],
|
65
|
+
['Maryland', 'MD'],
|
66
|
+
['Massachusetts', 'MA'],
|
67
|
+
['Michigan', 'MI'],
|
68
|
+
['Minnesota', 'MN'],
|
69
|
+
['Mississippi', 'MS'],
|
70
|
+
['Missouri', 'MO'],
|
71
|
+
['Montana', 'MT'],
|
72
|
+
['Nebraska', 'NE'],
|
73
|
+
['Nevada', 'NV'],
|
74
|
+
['New Hampshire', 'NH'],
|
75
|
+
['New Jersey', 'NJ'],
|
76
|
+
['New Mexico', 'NM'],
|
77
|
+
['New York', 'NY'],
|
78
|
+
['North Carolina', 'NC'],
|
79
|
+
['North Dakota', 'ND'],
|
80
|
+
['Ohio', 'OH'],
|
81
|
+
['Oklahoma', 'OK'],
|
82
|
+
['Oregon', 'OR'],
|
83
|
+
['Pennsylvania', 'PA'],
|
84
|
+
['Puerto Rico', 'PR'],
|
85
|
+
['Rhode Island', 'RI'],
|
86
|
+
['South Carolina', 'SC'],
|
87
|
+
['South Dakota', 'SD'],
|
88
|
+
['Tennessee', 'TN'],
|
89
|
+
['Texas', 'TX'],
|
90
|
+
['Utah', 'UT'],
|
91
|
+
['Vermont', 'VT'],
|
92
|
+
['Virginia', 'VA'],
|
93
|
+
['Washington', 'WA'],
|
94
|
+
['West Virginia', 'WV'],
|
95
|
+
['Wisconsin', 'WI'],
|
96
|
+
['Wyoming', 'WY']
|
97
|
+
]
|
98
|
+
if TbCheckout.config.canada_is_real
|
99
|
+
return {
|
100
|
+
'United States' => us_states,
|
101
|
+
'Canada' => [
|
102
|
+
['Alberta', 'AB'],
|
103
|
+
['British Columbia ', 'BC'],
|
104
|
+
['Manitoba', 'MB'],
|
105
|
+
['New Brunswick', 'NB'],
|
106
|
+
['Newfoundland and Labrador', 'NL'],
|
107
|
+
['Nova Scotia', 'NS'],
|
108
|
+
['Northwest Territories', 'NT'],
|
109
|
+
['Nunavut', 'NU'],
|
110
|
+
['Ontario', 'ON'],
|
111
|
+
['Prince Edward Island', 'PE'],
|
112
|
+
['Quebec', 'QC'],
|
113
|
+
['Saskatchewan', 'SK'],
|
114
|
+
['Yukon', 'YT']
|
115
|
+
]
|
116
|
+
}
|
117
|
+
else
|
118
|
+
return us_states
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return a list of credit card types for use with a form select
|
123
|
+
#
|
124
|
+
def tb_checkout_credit_card_types()
|
125
|
+
types = [
|
126
|
+
['Visa', 'visa'],
|
127
|
+
['MasterCard', 'master'],
|
128
|
+
['Discover', 'discover'],
|
129
|
+
['American Express', 'american_express']
|
130
|
+
]
|
131
|
+
types.select!{ |a, b|
|
132
|
+
TbCheckout.config.card_types.include?(b.to_sym)
|
133
|
+
}
|
134
|
+
return types
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module TbCheckout::BelongsToUserSession
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
belongs_to :spud_user
|
6
|
+
scope :for_user_or_session, ->(user_id, session_id){
|
7
|
+
where('(session_id = ?) OR (spud_user_id IS NOT NULL AND spud_user_id != 0 AND spud_user_id = ?)', session_id, user_id)
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def belongs_to?(user_id:nil, session_id:nil)
|
12
|
+
return (self.session_id == session_id) || (self.spud_user_id.present? && self.spud_user_id == user_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module TbCheckout::Purchasable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
has_many :cart_items, :class_name => 'TbCheckout::CartItem', :as => :item
|
6
|
+
tb_checkout_quantity_is_editable(true)
|
7
|
+
tb_checkout_delete_on_removal(false)
|
8
|
+
|
9
|
+
include ActiveSupport::Callbacks
|
10
|
+
define_callbacks :capture
|
11
|
+
set_callback :capture, :after, :tb_checkout_after_capture!
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
attr_reader :quantity_is_editable, :after_capture, :delete_on_removal, :url_builder, :cart_detail_view
|
16
|
+
|
17
|
+
# Configure whether or not the given class of product should support multiple quantities
|
18
|
+
# * Default: true
|
19
|
+
#
|
20
|
+
def tb_checkout_quantity_is_editable(is_editable)
|
21
|
+
@quantity_is_editable = is_editable
|
22
|
+
end
|
23
|
+
|
24
|
+
# Configure a hook to run after the corresponding transaction is successfully captured
|
25
|
+
# Use this to perform any cleanup tasks on the product model
|
26
|
+
#
|
27
|
+
# Ex:
|
28
|
+
# tb_checkout_after_capture ->(instance){
|
29
|
+
# instance.update_column(:is_captured, true)
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
def tb_checkout_after_capture(symbol_or_lambda)
|
33
|
+
@after_capture = symbol_or_lambda
|
34
|
+
end
|
35
|
+
|
36
|
+
# Configure whether or not the given class of product should self destruct when it is removed from the shopping cart
|
37
|
+
# For example, this may make sense for a purchasable record that is only used one time
|
38
|
+
# * Default: false
|
39
|
+
#
|
40
|
+
def tb_checkout_delete_on_removal(should_delete)
|
41
|
+
@delete_on_removal = should_delete
|
42
|
+
end
|
43
|
+
|
44
|
+
# Configure a callback that builds a URL for the given product page
|
45
|
+
#
|
46
|
+
# Ex:
|
47
|
+
# tb_checkout_url_builder ->(instance, context){
|
48
|
+
# context.widget_path(instance.id)
|
49
|
+
# }
|
50
|
+
def tb_checkout_url_builder(symbol_or_lambda)
|
51
|
+
@url_builder = symbol_or_lambda
|
52
|
+
end
|
53
|
+
|
54
|
+
# Configure a partial to be displayed in the shopping cart
|
55
|
+
#
|
56
|
+
def tb_checkout_cart_detail_view(detail_view)
|
57
|
+
@cart_detail_view = detail_view
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def tb_checkout_build_url(view)
|
62
|
+
url_builder = self.class.url_builder
|
63
|
+
if url_builder.is_a? Proc
|
64
|
+
return url_builder.call(self, view)
|
65
|
+
elsif url_builder.is_a? Symbol
|
66
|
+
return send(url_builder, view)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def tb_checkout_after_capture!
|
73
|
+
after_capture = self.class.after_capture
|
74
|
+
if after_capture.is_a? Proc
|
75
|
+
after_capture.call(self)
|
76
|
+
elsif after_capture.is_a? Symbol
|
77
|
+
send(after_capture)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module TbCheckout
|
2
|
+
class Cart < ActiveRecord::Base
|
3
|
+
self.table_name = 'tb_checkout_carts'
|
4
|
+
has_many :cart_items, :inverse_of => :cart
|
5
|
+
has_many :transactions, :inverse_of => :cart
|
6
|
+
|
7
|
+
scope :completed, ->{ where(:is_completed => true) }
|
8
|
+
scope :in_progress, ->{ where(:is_completed => false, :is_abandoned => false) }
|
9
|
+
scope :abandoned, ->{ where(:is_abandoned => true) }
|
10
|
+
|
11
|
+
include TbCheckout::BelongsToUserSession
|
12
|
+
|
13
|
+
# Look for carts that have outlived the configured TbCheckout.config.cart_lifespan value and mark them as abandoned
|
14
|
+
#
|
15
|
+
def self.check_abandoned!
|
16
|
+
carts = in_progress.where('updated_at < ?', DateTime.now - TbCheckout.config.cart_lifespan)
|
17
|
+
return carts.update_all(:is_abandoned => true)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Build a short description of the cart contents
|
21
|
+
#
|
22
|
+
def description
|
23
|
+
if cart_items.length == 0
|
24
|
+
desc = "This cart is currently empty"
|
25
|
+
elsif cart_items.length > 1
|
26
|
+
desc = "#{cart_items.first.item_description} and #{cart_items.length-1} other #{'item'.pluralize(cart_items.length-1)}"
|
27
|
+
else
|
28
|
+
desc = cart_items.first.item_description
|
29
|
+
end
|
30
|
+
return desc
|
31
|
+
end
|
32
|
+
|
33
|
+
def total_price
|
34
|
+
return self.cart_items.collect(&:total_price).sum()
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_to_cart(product, quantity: 1)
|
38
|
+
if !product.class.included_modules.map(&:to_s).include?("TbCheckout::Purchasable")
|
39
|
+
raise ArgumentError, 'product must conform to TbCheckout::Purchasable'
|
40
|
+
end
|
41
|
+
return self.cart_items.create({
|
42
|
+
:item => product,
|
43
|
+
:quantity => quantity
|
44
|
+
})
|
45
|
+
end
|
46
|
+
|
47
|
+
def is_empty?
|
48
|
+
return self.cart_items.count == 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def user_full_name
|
52
|
+
return self.spud_user.try(:full_name) || 'Anonymous'
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module TbCheckout
|
2
|
+
class CartItem < ActiveRecord::Base
|
3
|
+
self.table_name = 'tb_checkout_cart_items'
|
4
|
+
belongs_to :cart, :inverse_of => :cart_items, :touch => true
|
5
|
+
belongs_to :item, :polymorphic => true
|
6
|
+
|
7
|
+
validates :cart, :item, :presence => true
|
8
|
+
validates :quantity, :numericality => {:greater_than => 0}
|
9
|
+
before_save :set_item_values
|
10
|
+
after_destroy :cleanup_purchasable_item
|
11
|
+
|
12
|
+
def total_price
|
13
|
+
return item_price * quantity
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_item_url?
|
17
|
+
return self.item && self.item.class.url_builder.present?
|
18
|
+
end
|
19
|
+
|
20
|
+
def item_url(view)
|
21
|
+
return item.tb_checkout_build_url(view)
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_detail_view?
|
25
|
+
return self.item && self.item.class.cart_detail_view.present?
|
26
|
+
end
|
27
|
+
|
28
|
+
def detail_view
|
29
|
+
return self.item.class.cart_detail_view
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def set_item_values
|
35
|
+
if self.item
|
36
|
+
self.item_description = self.item.description
|
37
|
+
self.item_price = self.item.price
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def cleanup_purchasable_item
|
42
|
+
if self.item && self.item.class.delete_on_removal
|
43
|
+
self.item.destroy()
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|