tb_checkout 1.0.3
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 +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
|