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.
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,253 @@
1
+ module TbCheckout
2
+ class Transaction < ActiveRecord::Base
3
+ self.table_name = 'tb_checkout_transactions'
4
+ belongs_to :cart, :inverse_of => :transactions
5
+
6
+ attr_accessor :card_number
7
+ attr_accessor :card_ccv
8
+ attr_reader :card_expiration
9
+ self.columns_hash['card_expiration'] = OpenStruct.new(type: :date, klass: Date)
10
+
11
+ attr_reader :response
12
+
13
+ validates_presence_of :cart, :billing_first_name, :billing_last_name, :billing_address_1,
14
+ :billing_city, :billing_state, :billing_postal, :card_type
15
+ validates_numericality_of :amount_charged, :greater_than => 0
16
+ validate :credit_card_is_valid, :on => :create
17
+ before_validation :set_amount_charged
18
+ before_create :set_invoice_num, :set_spud_user_and_session_id
19
+
20
+ include TbCheckout::BelongsToUserSession
21
+
22
+ # Transaction status
23
+ #
24
+ # Status describes the current state of a transaction
25
+ #
26
+ # * Pending: The transaction is newly created
27
+ # * Authorized: The transaction has been submitted to the payment gateway and successfully authorized
28
+ # * Captured: The transaction has been submitted to the payment gateway and the purchase is finalized
29
+ # * Fail: A failure code was received from the payment gateway
30
+ # * Voided: The transaction is voided in the gateway
31
+ # * Refunded: The transaction is refunded in the gateway
32
+ module Status
33
+ PENDING = 'pending'
34
+ AUTHORIZED = 'authorized'
35
+ CAPTURED = 'captured'
36
+ FAIL = 'fail'
37
+ VOIDED = 'voided'
38
+ REFUNDED = 'refunded'
39
+ end
40
+
41
+ # Scopes
42
+ default_scope { order('created_at desc') }
43
+ scope :pending, ->{ where(:status => Status::PENDING) }
44
+ scope :authorized, ->{ where(:status => Status::AUTHORIZED) }
45
+ scope :captured, ->{ where(:status => Status::CAPTURED) }
46
+ scope :failed, ->{ where(:status => Status::FAIL) }
47
+
48
+ # Authorize the transaction against the payment gateway
49
+ #
50
+ # * The transaction must be in PENDING state in order ot take this action
51
+ def authorize!
52
+ self.with_lock(true) do
53
+ if self.status != Status::PENDING
54
+ raise StandardError, 'Payment must be in Pending state before authorization'
55
+ end
56
+ @response = TbCheckout.gateway.authorize(amount_in_cents, build_credit_card(), build_purchase_options())
57
+ if @response.success?
58
+ self.update_columns(:status => Status::AUTHORIZED, :gateway_transaction_id => @response.params['transaction_id'], :response_text => @response.to_json)
59
+ return true
60
+ else
61
+ self.update_columns(:status => Status::FAIL, :response_text => @response.to_json)
62
+ logger.fatal @response.inspect
63
+ return false
64
+ end
65
+ end
66
+ end
67
+
68
+ # Capture the funds from an authorized transaction through the payment gateway
69
+ #
70
+ # * The transaction must be in AUTHORIZED state in order ot take this action
71
+ def capture!
72
+ self.with_lock(true) do
73
+ if self.status != Status::AUTHORIZED
74
+ raise StandardError, 'Payment must be in Authorized state before capture'
75
+ end
76
+ @response = TbCheckout.gateway.capture(amount_in_cents, self.gateway_transaction_id)
77
+ if @response.success? || @response.params['action'] == 'PRIOR_AUTH_CAPTURE'
78
+ self.update_columns(:status => Status::CAPTURED, :response_text => @response.to_json)
79
+ else
80
+ self.update_columns(:status => Status::FAIL, :response_text => @response.to_json)
81
+ logger.fatal @response.inspect
82
+ end
83
+ end
84
+ if self.status == Status::CAPTURED
85
+ self.cart.update_attributes(:is_completed => true)
86
+ self.cart.cart_items.each do |cart_item|
87
+ cart_item.item.run_callbacks(:capture)
88
+ end
89
+ return true
90
+ else
91
+ return false
92
+ end
93
+ end
94
+
95
+ # Voids or Refunds the transaction
96
+ #
97
+ # * An unsettled trasanction cannot be refunded, and a settled transaction cannot be voided
98
+ # * Transactions are settled nightly
99
+ # * If the transaction is less than 24 hours old, attempt to void it and if that fails attempt a refund
100
+ def void_or_refund!
101
+ if (1.days.ago < self.created_at && void!) || refund!
102
+ return true
103
+ else
104
+ return false
105
+ end
106
+ end
107
+
108
+ def amount_in_cents
109
+ return amount_charged * 100
110
+ end
111
+
112
+ def billing_full_name
113
+ return "#{billing_first_name} #{billing_last_name}"
114
+ end
115
+
116
+ def billing_full_address
117
+ street = [billing_address_1, billing_address_2].reject(&:blank?).join(', ')
118
+ return "#{street}, #{billing_city} #{billing_state}, #{billing_postal}"
119
+ end
120
+
121
+ # Check for card_expiration value in String format and convert to Date format
122
+ #
123
+ def card_expiration=(date)
124
+ if date.is_a? String
125
+ begin
126
+ @card_expiration = Date.parse(date)
127
+ rescue ArgumentError => e
128
+ @card_expiration = nil
129
+ end
130
+ else
131
+ @card_expiration = date
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def void!
138
+ self.with_lock(true) do
139
+ if self.status != Status::CAPTURED
140
+ raise StandardError, 'Payment must be in captured state before it can be voided'
141
+ end
142
+ @response = TbCheckout.gateway.void(gateway_transaction_id)
143
+ if @response.success?
144
+ self.update_columns(:status => Status::VOIDED, :response_text => @response.to_json)
145
+ return true
146
+ else
147
+ self.update_columns(:response_text => @response.to_json)
148
+ logger.fatal @response.inspect
149
+ return false
150
+ end
151
+ end
152
+ end
153
+
154
+ def refund!
155
+ self.with_lock(true) do
156
+ if self.status != Status::CAPTURED
157
+ raise StandardError, 'Payment must be in captured state before it can be voided'
158
+ end
159
+ cc_last_four_digits = self.card_display.chars.last(4).join()
160
+ @response = TbCheckout.gateway.refund(amount_in_cents, self.gateway_transaction_id, {
161
+ :order_id => invoice_num,
162
+ :description => "REFUND: #{self.cart.description}",
163
+ :card_number => cc_last_four_digits,
164
+ :first_name => billing_first_name,
165
+ :last_name => billing_last_name,
166
+ :zip => billing_postal
167
+ })
168
+ if @response.success? || @response.params['response_reason_code'] == '55' # already refunded
169
+ self.update_columns(:status => Status::REFUNDED, :response_text => @response.to_json, :refund_gateway_transaction_id => @response.params['gateway_transaction_id'])
170
+ return true
171
+ else
172
+ self.update_columns(:response_text => @response.to_json)
173
+ logger.fatal @response.inspect
174
+ return false
175
+ end
176
+ end
177
+ end
178
+
179
+ def credit_card_is_valid
180
+ if self.card_number.blank?
181
+ errors.add(:card_number, 'cannot be blank')
182
+ end
183
+ if self.card_ccv.blank?
184
+ errors.add(:card_ccv, 'cannot be blank')
185
+ end
186
+ if errors.size == 0
187
+ credit_card = build_credit_card()
188
+ if credit_card.valid?()
189
+ self.card_display = credit_card.display_number()
190
+ else
191
+ credit_card.errors.full_messages.each do |msg|
192
+ errors.add(:base, msg)
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ def build_credit_card
199
+ return ActiveMerchant::Billing::CreditCard.new({
200
+ :brand => card_type,
201
+ :number => card_number,
202
+ :verification_value => card_ccv,
203
+ :month => card_expiration.month,
204
+ :year => card_expiration.year,
205
+ :first_name => billing_first_name,
206
+ :last_name => billing_last_name
207
+ })
208
+ end
209
+
210
+ def build_purchase_options
211
+ return {
212
+ :email => self.cart.spud_user.try(:email) || nil,
213
+ :order_id => self.invoice_num,
214
+ :description => self.cart.description,
215
+ :billing_address => {
216
+ :name => billing_full_name,
217
+ :address1 => billing_address_1,
218
+ :address2 => billing_address_2,
219
+ :city => billing_city,
220
+ :state => billing_state,
221
+ :country => country_for_billing_state(),
222
+ :zip => billing_postal,
223
+ :phone => self.cart.spud_user.try(:phone) || nil
224
+ }
225
+ }
226
+ end
227
+
228
+ def set_amount_charged
229
+ if self.cart
230
+ self.amount_charged = self.cart.total_price
231
+ end
232
+ end
233
+
234
+ def set_invoice_num
235
+ user_id = self.cart.spud_user_id || 0
236
+ self.invoice_num = [user_id, Time.now.to_i].join('-')
237
+ end
238
+
239
+ def set_spud_user_and_session_id
240
+ self.spud_user_id = self.cart.spud_user_id || 0
241
+ self.session_id = self.cart.session_id || 0
242
+ end
243
+
244
+ def country_for_billing_state
245
+ if ['AB', 'BC', 'MB', 'NB', 'NL', 'NS', 'NT', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT'].include?(billing_state)
246
+ return 'Canada'
247
+ else
248
+ return 'USA'
249
+ end
250
+ end
251
+
252
+ end
253
+ end
@@ -0,0 +1,54 @@
1
+ <% content_for :data_controls do %>
2
+ <%= form_tag tb_checkout_admin_carts_path, :method => :get, :class => 'tb-checkout-cart-filter-form' do %>
3
+ <%= select_tag :status, options_for_select([
4
+ ['All', 'all'],
5
+ ['Complete', 'completed'],
6
+ ['In Progress', 'in-progress'],
7
+ ['Abandoned', 'abandoned']
8
+ ], params[:status]) %>
9
+ <% end %>
10
+ <% end %>
11
+
12
+ <% content_for :detail do %>
13
+ <table class="table table-striped">
14
+ <thead>
15
+ <th>User</th>
16
+ <th>Description</th>
17
+ <th>Total Price</th>
18
+ <th>Status</th>
19
+ <th>Last Modified</th>
20
+ <th></th>
21
+ </thead>
22
+ <tbody>
23
+ <% @carts.each do |cart| %>
24
+ <tr>
25
+ <td class="no-wrap"><%= cart.user_full_name %></td>
26
+ <td><%= cart.description %></td>
27
+ <td>
28
+ <% if cart.is_empty? %>
29
+ Empty
30
+ <% else %>
31
+ <%= number_to_currency cart.total_price %>
32
+ <% end %>
33
+ </td>
34
+ <td>
35
+ <%= tb_checkout_status_label_for_cart(cart) %>
36
+ </td>
37
+ <td class="no-wrap"><%= cart.updated_at.strftime('%I:%M %p, %D') %></td>
38
+ <td>
39
+ <%= link_to 'Detail', tb_checkout_admin_cart_path(cart, :status => params[:status]), :class => 'btn btn-mini' %>
40
+ </td>
41
+ </tr>
42
+ <% end %>
43
+ </tbody>
44
+ </table>
45
+ <%= will_paginate @carts %>
46
+
47
+ <script>
48
+ $(document).ready(function(){
49
+ $('.tb-checkout-cart-filter-form').on('change', 'select', function(e){
50
+ $(this).parents('form').submit();
51
+ });
52
+ });
53
+ </script>
54
+ <% end %>
@@ -0,0 +1,45 @@
1
+ <h3>General Info</h3>
2
+
3
+ <ul>
4
+ <li><strong>User</strong> <%= @cart.user_full_name %></li>
5
+ <li><strong>Created At</strong> <%= @cart.created_at.strftime('%I:%M %p, %D') %></li>
6
+ <li><strong>Last Modified</strong> <%= @cart.updated_at.strftime('%I:%M %p, %D') %></li>
7
+ <li><strong>Status</strong> <%= tb_checkout_status_label_for_cart(@cart) %></li>
8
+ </ul>
9
+
10
+ <h3>Products</h3>
11
+
12
+ <% if @cart.is_empty? %>
13
+ <p>This cart does not contain any items.</p>
14
+ <% else %>
15
+ <table class="table table-striped">
16
+ <thead>
17
+ <th>Product</th>
18
+ <th>Quantity</th>
19
+ <th>Price</th>
20
+ </thead>
21
+ <tbody>
22
+ <% @cart.cart_items.each do |cart_item| %>
23
+ <tr>
24
+ <td><%= cart_item.item_description %></td>
25
+ <td><%= cart_item.quantity %></td>
26
+ <td><%= number_to_currency cart_item.total_price %></td>
27
+ </tr>
28
+ <% end %>
29
+ </tbody>
30
+ <tfoot>
31
+ <tr>
32
+ <td colspan="2">Total</td>
33
+ <td><%= number_to_currency @cart.total_price %></td>
34
+ </tr>
35
+ </tfoot>
36
+ </table>
37
+ <% end %>
38
+
39
+ <h3>Transactions</h3>
40
+
41
+ <% if @cart.transactions.blank? %>
42
+ <p>There are no transactions associated with this shopping cart.</p>
43
+ <% else %>
44
+ <%= render :partial => '/tb_checkout/admin/transactions/table', :locals => {:transactions => @cart.transactions} %>
45
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <table class="table table-striped">
2
+ <thead>
3
+ <th>Invoice Number</th>
4
+ <th>Transaction ID</th>
5
+ <th>Billing Name</th>
6
+ <th>Card</th>
7
+ <th>Amount Charged</th>
8
+ <th>Status</th>
9
+ <th></th>
10
+ </thead>
11
+ <tbody>
12
+ <% transactions.each do |transaction| %>
13
+ <tr>
14
+ <td><%= transaction.invoice_num %></td>
15
+ <td><%= transaction.gateway_transaction_id %></td>
16
+ <td class="no-wrap"><%= transaction.billing_full_name %></td>
17
+ <td class="no-wrap"><%= transaction.card_display %> (<%= transaction.card_type %>)</td>
18
+ <td><%= number_to_currency transaction.amount_charged %></td>
19
+ <td><%= tb_checkout_status_label_for_transaction(transaction.status) %></td>
20
+ <td><%= link_to 'Detail', tb_checkout_admin_transaction_path(transaction), :class => 'btn btn-mini' %></td>
21
+ </tr>
22
+ <% end %>
23
+ </tbody>
24
+ </table>
@@ -0,0 +1,7 @@
1
+ <% content_for :data_controls do %>
2
+
3
+ <% end %>
4
+
5
+ <% content_for :detail do %>
6
+ <%= render :partial => 'table', :locals => {:transactions => @transactions} %>
7
+ <% end %>
@@ -0,0 +1,70 @@
1
+ <h3>Summary</h3>
2
+ <table class="table table-bordered">
3
+ <thead>
4
+ <tr>
5
+ <th>User</th>
6
+ <th>Invoice Number</th>
7
+ <th>Transaction ID</th>
8
+ <th>Card</th>
9
+ <th>Amount Charged</th>
10
+ <th>Status</th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <td><%= @transaction.cart.user_full_name %></td>
15
+ <td><%= @transaction.invoice_num %></td>
16
+ <td><%= @transaction.gateway_transaction_id %></td>
17
+ <td><%= @transaction.card_display %> (<%= @transaction.card_type %>)</td>
18
+ <td><%= number_to_currency @transaction.amount_charged %></td>
19
+ <td><%= tb_checkout_status_label_for_transaction(@transaction.status) %></td>
20
+ </tbody>
21
+ </table>
22
+
23
+ <h3>Billing Address</h3>
24
+ <table class="table table-bordered">
25
+ <thead>
26
+ <tr>
27
+ <th>Name</th>
28
+ <th>Address</th>
29
+ <th>Address 2</th>
30
+ <th>City</th>
31
+ <th>State</th>
32
+ <th>Postal</th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ <tr>
37
+ <td><%= @transaction.billing_full_name %></td>
38
+ <td><%= @transaction.billing_address_1 %></td>
39
+ <td><%= @transaction.billing_address_2 || "--" %></td>
40
+ <td><%= @transaction.billing_city %></td>
41
+ <td><%= @transaction.billing_state %></td>
42
+ <td><%= @transaction.billing_postal %></td>
43
+ </tr>
44
+ </table>
45
+
46
+ <h3>Line Items</h3>
47
+ <table class="table table-bordered">
48
+ <thead>
49
+ <tr>
50
+ <th>Product</th>
51
+ <th>Quantity</th>
52
+ <th>Price</th>
53
+ </tr>
54
+ </thead>
55
+ <tbody>
56
+ <% @transaction.cart.cart_items.each do |cart_item| %>
57
+ <tr>
58
+ <td><%= cart_item.item_description %></td>
59
+ <td><%= cart_item.quantity %></td>
60
+ <td><%= number_to_currency cart_item.total_price %></td>
61
+ </tr>
62
+ <% end %>
63
+ </tbody>
64
+ <tfoot>
65
+ <tr>
66
+ <td colspan="2">Total</td>
67
+ <td><%= number_to_currency @transaction.cart.total_price %></td>
68
+ </tr>
69
+ </tfoot>
70
+ </table>