tb_checkout 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +162 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/images/tb_checkout/admin/shopping_carts.png +0 -0
  6. data/app/assets/images/tb_checkout/admin/transactions.png +0 -0
  7. data/app/assets/javascripts/tb_checkout/admin/carts.js +2 -0
  8. data/app/assets/javascripts/tb_checkout/admin/transactions.js +2 -0
  9. data/app/assets/javascripts/tb_checkout/application.js +13 -0
  10. data/app/assets/javascripts/tb_checkout/cart_items.js +2 -0
  11. data/app/assets/javascripts/tb_checkout/carts.js +2 -0
  12. data/app/assets/javascripts/tb_checkout/transactions.js +2 -0
  13. data/app/assets/stylesheets/tb_checkout/admin/carts.css +4 -0
  14. data/app/assets/stylesheets/tb_checkout/admin/transactions.css +4 -0
  15. data/app/assets/stylesheets/tb_checkout/application.css +15 -0
  16. data/app/assets/stylesheets/tb_checkout/cart_items.css +4 -0
  17. data/app/assets/stylesheets/tb_checkout/carts.css +4 -0
  18. data/app/assets/stylesheets/tb_checkout/transactions.css +4 -0
  19. data/app/controllers/concerns/tb_checkout/controller_helpers.rb +16 -0
  20. data/app/controllers/tb_checkout/admin/carts_controller.rb +39 -0
  21. data/app/controllers/tb_checkout/admin/transactions_controller.rb +26 -0
  22. data/app/controllers/tb_checkout/application_controller.rb +6 -0
  23. data/app/controllers/tb_checkout/cart_items_controller.rb +63 -0
  24. data/app/controllers/tb_checkout/carts_controller.rb +8 -0
  25. data/app/controllers/tb_checkout/transactions_controller.rb +87 -0
  26. data/app/helpers/tb_checkout/admin/carts_helper.rb +13 -0
  27. data/app/helpers/tb_checkout/admin/transactions_helper.rb +16 -0
  28. data/app/helpers/tb_checkout/application_helper.rb +138 -0
  29. data/app/helpers/tb_checkout/transactions_helper.rb +2 -0
  30. data/app/models/concerns/tb_checkout/belongs_to_user_session.rb +15 -0
  31. data/app/models/concerns/tb_checkout/purchasable.rb +81 -0
  32. data/app/models/tb_checkout/basic_product.rb +6 -0
  33. data/app/models/tb_checkout/cart.rb +56 -0
  34. data/app/models/tb_checkout/cart_item.rb +48 -0
  35. data/app/models/tb_checkout/transaction.rb +253 -0
  36. data/app/views/tb_checkout/admin/carts/index.html.erb +54 -0
  37. data/app/views/tb_checkout/admin/carts/show.html.erb +45 -0
  38. data/app/views/tb_checkout/admin/transactions/_table.html.erb +24 -0
  39. data/app/views/tb_checkout/admin/transactions/index.html.erb +7 -0
  40. data/app/views/tb_checkout/admin/transactions/show.html.erb +70 -0
  41. data/app/views/tb_checkout/cart_items/create.html.erb +2 -0
  42. data/app/views/tb_checkout/cart_items/destroy.html.erb +2 -0
  43. data/app/views/tb_checkout/cart_items/update.html.erb +2 -0
  44. data/app/views/tb_checkout/carts/_cart.html.erb +65 -0
  45. data/app/views/tb_checkout/carts/show.html.erb +10 -0
  46. data/app/views/tb_checkout/transactions/_details.html.erb +17 -0
  47. data/app/views/tb_checkout/transactions/confirm.html.erb +11 -0
  48. data/app/views/tb_checkout/transactions/index.html.erb +29 -0
  49. data/app/views/tb_checkout/transactions/new.html.erb +90 -0
  50. data/app/views/tb_checkout/transactions/show.html.erb +14 -0
  51. data/config/locales/en.yml +23 -0
  52. data/config/routes.rb +20 -0
  53. data/db/migrate/20140703185256_create_tb_checkout_carts.rb +12 -0
  54. data/db/migrate/20140703185258_create_tb_checkout_cart_items.rb +15 -0
  55. data/db/migrate/20140703185259_create_tb_checkout_transactions.rb +23 -0
  56. data/db/migrate/20140707142350_create_tb_checkout_basic_products.rb +8 -0
  57. data/db/migrate/20140915202848_add_spud_user_and_session_id_to_transactions.rb +10 -0
  58. data/lib/tasks/tb_checkout_tasks.rake +19 -0
  59. data/lib/tb_checkout.rb +4 -0
  60. data/lib/tb_checkout/configuration.rb +8 -0
  61. data/lib/tb_checkout/engine.rb +36 -0
  62. data/lib/tb_checkout/schema.rb +51 -0
  63. data/lib/tb_checkout/version.rb +3 -0
  64. data/spec/controllers/tb_checkout/carts_controller_spec.rb +11 -0
  65. data/spec/controllers/tb_checkout/transactions_controller_spec.rb +110 -0
  66. data/spec/dummy/README.rdoc +28 -0
  67. data/spec/dummy/Rakefile +6 -0
  68. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  69. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  70. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  71. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  72. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  73. data/spec/dummy/bin/bundle +3 -0
  74. data/spec/dummy/bin/rails +4 -0
  75. data/spec/dummy/bin/rake +4 -0
  76. data/spec/dummy/config.ru +4 -0
  77. data/spec/dummy/config/application.rb +30 -0
  78. data/spec/dummy/config/boot.rb +5 -0
  79. data/spec/dummy/config/database.yml +13 -0
  80. data/spec/dummy/config/environment.rb +5 -0
  81. data/spec/dummy/config/environments/development.rb +37 -0
  82. data/spec/dummy/config/environments/production.rb +82 -0
  83. data/spec/dummy/config/environments/test.rb +39 -0
  84. data/spec/dummy/config/initializers/assets.rb +8 -0
  85. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  86. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  87. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  88. data/spec/dummy/config/initializers/inflections.rb +16 -0
  89. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  90. data/spec/dummy/config/initializers/session_store.rb +3 -0
  91. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  92. data/spec/dummy/config/locales/en.yml +23 -0
  93. data/spec/dummy/config/routes.rb +4 -0
  94. data/spec/dummy/config/secrets.yml +22 -0
  95. data/spec/dummy/db/migrate/20140915202259_create_spud_admin_permissions.tb_core.rb +12 -0
  96. data/spec/dummy/db/migrate/20140915202260_create_spud_users.tb_core.rb +30 -0
  97. data/spec/dummy/db/migrate/20140915202261_add_time_zone_to_spud_user.tb_core.rb +7 -0
  98. data/spec/dummy/db/migrate/20140915202262_add_scope_to_spud_admin_permissions.tb_core.rb +7 -0
  99. data/spec/dummy/db/migrate/20140915202263_create_spud_user_settings.tb_core.rb +12 -0
  100. data/spec/dummy/db/migrate/20140915202264_create_spud_roles.tb_core.rb +11 -0
  101. data/spec/dummy/db/migrate/20140915202265_create_spud_permissions.tb_core.rb +11 -0
  102. data/spec/dummy/db/migrate/20140915202266_create_spud_role_permissions.tb_core.rb +12 -0
  103. data/spec/dummy/db/migrate/20140915202267_drop_spud_admin_permissions.tb_core.rb +16 -0
  104. data/spec/dummy/db/migrate/20140915202268_create_tb_checkout_carts.tb_checkout_engine.rb +13 -0
  105. data/spec/dummy/db/migrate/20140915202269_create_tb_checkout_cart_items.tb_checkout_engine.rb +16 -0
  106. data/spec/dummy/db/migrate/20140915202270_create_tb_checkout_transactions.tb_checkout_engine.rb +24 -0
  107. data/spec/dummy/db/migrate/20140915202271_create_tb_checkout_basic_products.tb_checkout_engine.rb +9 -0
  108. data/spec/dummy/db/migrate/20140915234534_add_spud_user_and_session_id_to_transactions.tb_checkout_engine.rb +11 -0
  109. data/spec/dummy/db/schema.rb +135 -0
  110. data/spec/dummy/public/404.html +67 -0
  111. data/spec/dummy/public/422.html +67 -0
  112. data/spec/dummy/public/500.html +66 -0
  113. data/spec/dummy/public/favicon.ico +0 -0
  114. data/spec/factories/spud_users.rb +16 -0
  115. data/spec/factories/tb_checkout_basic_products.rb +8 -0
  116. data/spec/factories/tb_checkout_cart_items.rb +11 -0
  117. data/spec/factories/tb_checkout_carts.rb +14 -0
  118. data/spec/factories/tb_checkout_transactions.rb +37 -0
  119. data/spec/models/tb_checkout/transaction_spec.rb +5 -0
  120. data/spec/rails_helper.rb +73 -0
  121. data/spec/spec_helper.rb +85 -0
  122. metadata +335 -0
@@ -0,0 +1,8 @@
1
+ class TbCheckout::CartsController < TbCheckout::ApplicationController
2
+
3
+ def show
4
+ @cart = tb_checkout_current_cart
5
+ respond_with @cart
6
+ end
7
+
8
+ end
@@ -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,2 @@
1
+ module TbCheckout::TransactionsHelper
2
+ 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,6 @@
1
+ module TbCheckout
2
+ class BasicProduct < ActiveRecord::Base
3
+ self.table_name = 'tb_checkout_basic_products'
4
+ include TbCheckout::Purchasable
5
+ end
6
+ 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