spree_abandoned_cart 0.3.0 → 0.3.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 +4 -4
- data/.gitignore +17 -0
- data/.gitlab-ci.yml +15 -0
- data/.rspec +1 -0
- data/Gemfile +8 -0
- data/LICENSE +26 -0
- data/README.md +69 -0
- data/Rakefile +15 -0
- data/app/controllers/spree/admin/abandoned_orders_controller.rb +35 -0
- data/app/mailers/spree/abandoned_cart_mailer.rb +20 -0
- data/app/models/order_decorator.rb +38 -0
- data/app/models/order_updater_decorator.rb +25 -0
- data/app/models/spree/abandoned_cart_email_configuration.rb +5 -0
- data/app/models/spree/abandoned_order.rb +30 -0
- data/app/overrides/add_button_to_orders_tab_for_abandoned_index_page.rb +8 -0
- data/app/views/spree/abandoned_cart_mailer/abandoned_email.html.erb +145 -0
- data/app/views/spree/abandoned_cart_mailer/abandoned_second_email.html.erb +145 -0
- data/app/views/spree/abandoned_cart_mailer/abandoned_third_email.html.erb +145 -0
- data/app/views/spree/admin/abandoned_orders/_filters_bar.html.haml +49 -0
- data/app/views/spree/admin/abandoned_orders/_list.html.haml +22 -0
- data/app/views/spree/admin/abandoned_orders/_list_respond.js.erb +1 -0
- data/app/views/spree/admin/abandoned_orders/_order_summary.html.haml +16 -0
- data/app/views/spree/admin/abandoned_orders/_titles.html.haml +6 -0
- data/app/views/spree/admin/abandoned_orders/index.html.haml +3 -0
- data/app/views/spree/admin/abandoned_orders/index.js.haml +1 -0
- data/app/views/spree/admin/abandoned_orders/show.html.haml +2 -0
- data/config/initializers/load_preferences.rb +5 -0
- data/config/locales/en.yml +17 -0
- data/config/locales/ru.yml +17 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20130722151112_add_abandoned_email_sent_at_to_spree_orders.rb +5 -0
- data/db/migrate/20150825122125_create_spree_abandoned_orders.rb +12 -0
- data/lib/generators/spree_abandoned_cart/install/install_generator.rb +21 -0
- data/lib/spree_abandoned_cart.rb +3 -0
- data/lib/spree_abandoned_cart/engine.rb +22 -0
- data/lib/spree_abandoned_cart/factories.rb +6 -0
- data/script/rails +7 -0
- data/spec/models/order_spec.rb +35 -0
- data/spec/spec_helper.rb +86 -0
- data/spree_abandoned_cart.gemspec +38 -0
- metadata +68 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f4079aa9bcd323157d4a9066b95c195d3f7ad11
|
|
4
|
+
data.tar.gz: 5cdbf279a566c2a57829539d77a2c02f02618c9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d6cd065fa913f3e49bfd3ef90258606d2d004ca574d0bc43872a2e5e288f712bd5e3f0be7e99f38b9d570762febc1135afabf5a65662fdf4673a02f21211721
|
|
7
|
+
data.tar.gz: 8863266e96146919e7987b2295400c2e0854ce20d937e81cb3126b1102b99171753c1fb107014f7bd2a9d0d980ec653c0038c6ed8887f907c4ffbe4305620934
|
data/.gitignore
ADDED
data/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
image: ruby:2.4.2
|
|
2
|
+
|
|
3
|
+
cache:
|
|
4
|
+
paths:
|
|
5
|
+
- cache
|
|
6
|
+
|
|
7
|
+
before_script:
|
|
8
|
+
- apt-get update -q && apt-get install nodejs -yqq
|
|
9
|
+
- gem install bundler -v 1.15.4 --no-ri --no-rdoc
|
|
10
|
+
- bundle install -j $(nproc) --path cache
|
|
11
|
+
|
|
12
|
+
test:
|
|
13
|
+
script:
|
|
14
|
+
- bundle exec rake test_app
|
|
15
|
+
- rspec spec
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright (c) 2013 [name of plugin creator]
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
this list of conditions and the following disclaimer.
|
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
* Neither the name Spree nor the names of its contributors may be used to
|
|
13
|
+
endorse or promote products derived from this software without specific
|
|
14
|
+
prior written permission.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
## SpreeAbandonedCartEmail
|
|
2
|
+
|
|
3
|
+
Abandoned cart email extension. Looks for orders with line items that were created over 12 hours ago and gives them a nudge to complete.
|
|
4
|
+
|
|
5
|
+
Spree Commerce versions:
|
|
6
|
+
|
|
7
|
+
- Spree Core - 3.6
|
|
8
|
+
- Spree Auth Devise - 3.3
|
|
9
|
+
|
|
10
|
+
Use the config values below to change settings:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Spree::AbandonedCartEmailConfig::Config.email_timeframe_from, :integer
|
|
14
|
+
Spree::AbandonedCartEmailConfig::Config.email_timeframe_to, :integer
|
|
15
|
+
|
|
16
|
+
Spree::AbandonedCartEmail::Config.email_from = "spree@example.com"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Override the email view to customise:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
app/views/spree/abandoned_cart_mailer/abandoned_email.html.erb
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Create a rake task to send the email:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
desc "Abandoned cart emails"
|
|
29
|
+
task send_abandond_cart: :environment do
|
|
30
|
+
Spree::Order.email_eligible_abandoned_email_orders
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
Create a cronjob to run every so often to send the emails:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
0 * * * * /bin/bash -l -c 'cd {your_application_home} && RAILS_ENV=production rake send_abandoned_cart 2>&1'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Add spree_abandoned_cart to your Gemfile:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
gem 'spree_abandoned_cart'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Bundle your dependencies and run the installation generator:
|
|
48
|
+
|
|
49
|
+
```shell
|
|
50
|
+
bundle
|
|
51
|
+
bundle exec rails g spree_abandoned_cart:install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Testing
|
|
55
|
+
|
|
56
|
+
Be sure to bundle your dependencies and then create a dummy test app for the specs to run against.
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
bundle
|
|
60
|
+
bundle exec rake test_app
|
|
61
|
+
bundle exec rspec spec
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When testing your applications integration with this extension you may use it's factories.
|
|
65
|
+
Simply add this require statement to your spec_helper:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
require 'spree_abandoned_cart/factories'
|
|
69
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'bundler'
|
|
2
|
+
Bundler::GemHelper.install_tasks
|
|
3
|
+
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
|
+
require 'spree/testing_support/extension_rake'
|
|
6
|
+
|
|
7
|
+
RSpec::Core::RakeTask.new
|
|
8
|
+
|
|
9
|
+
task :default => [:spec]
|
|
10
|
+
|
|
11
|
+
desc 'Generates a dummy app for testing'
|
|
12
|
+
task :test_app do
|
|
13
|
+
ENV['LIB_NAME'] = 'spree_abandoned_cart'
|
|
14
|
+
Rake::Task['extension:test_app'].invoke
|
|
15
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class Spree::Admin::AbandonedOrdersController < Spree::Admin::BaseController
|
|
2
|
+
before_action :find_abandoned_cart, except: [:index ]
|
|
3
|
+
respond_to :js, only: [:index]
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
params[:q] ||= {}
|
|
7
|
+
|
|
8
|
+
params_clone = params[:q].deep_dup
|
|
9
|
+
|
|
10
|
+
if params_clone[:created_at_gt].present?
|
|
11
|
+
params_clone[:created_at_gt] = Time.zone.parse(params_clone[:created_at_gt]).beginning_of_day rescue ''
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if params_clone[:created_at_lt].present?
|
|
15
|
+
params_clone[:created_at_lt] = Time.zone.parse(params_clone[:created_at_lt]).end_of_day rescue ''
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
params_clone[:send_emails_count] = Integer(params_clone[:send_emails_count]) rescue ''
|
|
19
|
+
|
|
20
|
+
@search = Spree::AbandonedOrder.ransack(params_clone)
|
|
21
|
+
@abandoned_carts = @search.result(distinct: true).page(params[:page]).per(Spree::Config[:admin_products_per_page])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def destroy
|
|
25
|
+
@abandoned_cart.destroy
|
|
26
|
+
|
|
27
|
+
redirect_to admin_abandoned_orders_path
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def find_abandoned_cart
|
|
33
|
+
@abandoned_cart = Spree::AbandonedOrder.find(params[:id])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class AbandonedCartMailer < ActionMailer::Base
|
|
3
|
+
default from: Spree::AbandonedCartEmailConfig::Config.email_from
|
|
4
|
+
|
|
5
|
+
def abandoned_email(order)
|
|
6
|
+
@order = order
|
|
7
|
+
mail to: @order.email, subject: Spree.t('abandoned_cart.email.subject')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def abandoned_second_email(order)
|
|
11
|
+
@order = order
|
|
12
|
+
mail to: @order.email, subject: Spree.t('abandoned_cart.email.subject_second')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def abandoned_third_email(order)
|
|
16
|
+
@order = order
|
|
17
|
+
mail to: @order.email, subject: Spree.t('abandoned_cart.email.subject_third')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Spree::Order.class_eval do
|
|
2
|
+
has_one :abandoned_order, class_name: 'Spree::AbandonedOrder', foreign_key: :spree_order_id, dependent: :destroy
|
|
3
|
+
|
|
4
|
+
def self.email_eligible_abandoned_email_orders
|
|
5
|
+
eligible_abandoned_email_orders.each {|o| o.send_abandoned_email }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.eligible_abandoned_email_orders
|
|
9
|
+
where('state != ?
|
|
10
|
+
AND (payment_state IS NULL OR payment_state != ?)
|
|
11
|
+
AND email is NOT NULL
|
|
12
|
+
AND abandoned_email_sent_at IS NULL
|
|
13
|
+
AND (spree_orders.created_at BETWEEN ? AND ? )',
|
|
14
|
+
'complete',
|
|
15
|
+
'paid',
|
|
16
|
+
(Time.zone.now - Spree::AbandonedCartEmailConfig::Config.email_timeframe_from),
|
|
17
|
+
(Time.zone.now - Spree::AbandonedCartEmailConfig::Config.email_timeframe_to)).joins(:line_items).distinct
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def send_abandoned_email
|
|
21
|
+
# Don't send anything if the order has no line items.
|
|
22
|
+
return if line_items.empty?
|
|
23
|
+
|
|
24
|
+
Spree::AbandonedCartMailer.abandoned_email(self).deliver_now
|
|
25
|
+
mark_abandoned_email_as_sent
|
|
26
|
+
log_abandoned_cart
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def mark_abandoned_email_as_sent
|
|
32
|
+
update_column :abandoned_email_sent_at, Time.zone.now
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def log_abandoned_cart
|
|
36
|
+
Spree::AbandonedOrder.create spree_order_id: id
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Spree::OrderUpdater.class_eval do
|
|
2
|
+
|
|
3
|
+
def update_payment_state
|
|
4
|
+
last_state = order.payment_state
|
|
5
|
+
|
|
6
|
+
if payments.present? && payments.valid.size == 0
|
|
7
|
+
order.payment_state = 'failed'
|
|
8
|
+
elsif order.state == 'canceled' && order.payment_total == 0
|
|
9
|
+
order.payment_state = 'void'
|
|
10
|
+
else
|
|
11
|
+
order.payment_state = 'balance_due' if order.outstanding_balance > 0
|
|
12
|
+
order.payment_state = 'credit_owed' if order.outstanding_balance < 0
|
|
13
|
+
|
|
14
|
+
if !order.outstanding_balance?
|
|
15
|
+
order.payment_state = 'paid'
|
|
16
|
+
# Update abandoned order after changing order status to 'paid'
|
|
17
|
+
if order.abandoned_order.present?
|
|
18
|
+
order.abandoned_order.update(order_accomplished: true, accomplished_at: Time.zone.now)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
order.state_changed('payment') if last_state != order.payment_state
|
|
23
|
+
order.payment_state
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class Spree::AbandonedOrder < ActiveRecord::Base
|
|
2
|
+
belongs_to :order, class_name: 'Spree::Order', foreign_key: :spree_order_id
|
|
3
|
+
|
|
4
|
+
after_create :increment_send_emails_count
|
|
5
|
+
|
|
6
|
+
def self.email_eligible_abandoned_email_orders(email_number=1)
|
|
7
|
+
eligible_orders_next_attempt(email_number).each { |abandoned_order| abandoned_order.process_abandoned_email(email_number) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.eligible_orders_next_attempt(emails_count = 1, hours_period = [25, 23])
|
|
11
|
+
hours_period = [3, 1] unless ENV['RAILS_ENV'] == 'production'
|
|
12
|
+
|
|
13
|
+
where(send_emails_count: emails_count,
|
|
14
|
+
order_accomplished: false,
|
|
15
|
+
updated_at: (Time.zone.now - hours_period[0].hours)..(Time.zone.now - hours_period[1].hours))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process_abandoned_email(email_number=1)
|
|
19
|
+
(delete && return) unless self.order.email.present?
|
|
20
|
+
|
|
21
|
+
Spree::AbandonedCartMailer.send("abandoned_#{email_number == 1 ? 'second' : 'third'}_email", self.order).deliver_now
|
|
22
|
+
increment_send_emails_count
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def increment_send_emails_count
|
|
28
|
+
increment!(:send_emails_count)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Deface::Override.new(
|
|
2
|
+
virtual_path: 'spree/admin/orders/index',
|
|
3
|
+
name: 'admin_abandoned_index_button',
|
|
4
|
+
insert_before: "erb[loud]:contains('Spree.t(:new_order)')",
|
|
5
|
+
text: "<%= button_link_to Spree.t(:abandoned_index),
|
|
6
|
+
admin_abandoned_orders_path, { style: 'margin-right:5px;',
|
|
7
|
+
class: 'btn-primary', icon: 'ok', id: 'admin_abandoned_index' }%>"
|
|
8
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
5
|
+
<title><%= Spree.t("abandoned_cart.email.subject") %></title>
|
|
6
|
+
<style type="text/css">
|
|
7
|
+
#outlook a{padding:0;} /* Force Outlook to provide a "view in browser" button. */
|
|
8
|
+
body{width:100% !important;} .ReadMsgBody{width:100%;} .ExternalClass{width:100%;} /* Force Hotmail to display emails at full width */
|
|
9
|
+
body{-webkit-text-size-adjust:none;} /* Prevent Webkit platforms from changing default text sizes. */
|
|
10
|
+
|
|
11
|
+
body{font-family:Arial;margin:0; padding:0;}
|
|
12
|
+
img{border:0; height:auto; line-height:100%; outline:none; text-decoration:none;}
|
|
13
|
+
table td{border-collapse:collapse;}
|
|
14
|
+
#backgroundTable{height:100% !important; margin:0; padding:0; width:100% !important;}
|
|
15
|
+
|
|
16
|
+
body, #backgroundTable{
|
|
17
|
+
background-color:#FAFAFA;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#templateContainer{
|
|
21
|
+
border: 1px solid #DDDDDD;
|
|
22
|
+
margin-top: 10px;
|
|
23
|
+
padding: 20px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
h1, .h1{
|
|
27
|
+
color:#202020;
|
|
28
|
+
display:block;
|
|
29
|
+
font-family:Arial;
|
|
30
|
+
font-size:34px;
|
|
31
|
+
font-weight:bold;
|
|
32
|
+
line-height:100%;
|
|
33
|
+
margin-top:0;
|
|
34
|
+
margin-right:0;
|
|
35
|
+
margin-bottom:10px;
|
|
36
|
+
margin-left:0;
|
|
37
|
+
text-align:left;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
h2, .h2{
|
|
41
|
+
color:#202020;
|
|
42
|
+
display:block;
|
|
43
|
+
font-family:Arial;
|
|
44
|
+
font-size:30px;
|
|
45
|
+
font-weight:bold;
|
|
46
|
+
line-height:100%;
|
|
47
|
+
margin-top:0;
|
|
48
|
+
margin-right:0;
|
|
49
|
+
margin-bottom:10px;
|
|
50
|
+
margin-left:0;
|
|
51
|
+
text-align:left;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
h3, .h3{
|
|
55
|
+
color:#202020;
|
|
56
|
+
display:block;
|
|
57
|
+
font-family:Arial;
|
|
58
|
+
font-size:26px;
|
|
59
|
+
font-weight:bold;
|
|
60
|
+
line-height:100%;
|
|
61
|
+
margin-top:0;
|
|
62
|
+
margin-right:0;
|
|
63
|
+
margin-bottom:10px;
|
|
64
|
+
margin-left:0;
|
|
65
|
+
text-align:left;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
h4, .h4{
|
|
69
|
+
color:#202020;
|
|
70
|
+
display:block;
|
|
71
|
+
font-family:Arial;
|
|
72
|
+
font-size:22px;
|
|
73
|
+
font-weight:bold;
|
|
74
|
+
line-height:100%;
|
|
75
|
+
margin-top:0;
|
|
76
|
+
margin-right:0;
|
|
77
|
+
margin-bottom:10px;
|
|
78
|
+
margin-left:0;
|
|
79
|
+
text-align:left;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#templateContainer, .bodyContent{
|
|
83
|
+
background-color:#FFFFFF;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.bodyContent div{
|
|
87
|
+
color:#505050;
|
|
88
|
+
font-family:Arial;
|
|
89
|
+
font-size:14px;
|
|
90
|
+
line-height:150%;
|
|
91
|
+
text-align:left;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.bodyContent div a:link, .bodyContent div a:visited, /* Yahoo! Mail Override */ .bodyContent div a .yshortcuts /* Yahoo! Mail Override */{
|
|
95
|
+
color:#336699;
|
|
96
|
+
font-weight:normal;
|
|
97
|
+
text-decoration:underline;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.bodyContent img{
|
|
101
|
+
display:inline;
|
|
102
|
+
height:auto;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#items {
|
|
106
|
+
margin: 10px 0;
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">
|
|
111
|
+
<center>
|
|
112
|
+
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="backgroundTable">
|
|
113
|
+
<tr>
|
|
114
|
+
<td align="center" valign="top">
|
|
115
|
+
<table border="0" cellpadding="0" cellspacing="0" width="600" id="templateContainer">
|
|
116
|
+
<tr>
|
|
117
|
+
<td align="center" valign="top">
|
|
118
|
+
<table border="0" cellpadding="0" cellspacing="0" width="600" id="templateBody">
|
|
119
|
+
<tr>
|
|
120
|
+
<td valign="top" class="bodyContent">
|
|
121
|
+
We noticed you still have some things in your shopping cart — if you need help with sizes or have questions, please call, email, or chat and we'll do our best.
|
|
122
|
+
|
|
123
|
+
<table cellpadding="0" cellspacing="0" border="0" align="center" id="items">
|
|
124
|
+
<% @order.line_items.each do |line_item| %>
|
|
125
|
+
<% variant = line_item.variant %>
|
|
126
|
+
<tr>
|
|
127
|
+
<td class="cart-item-description" data-hook="cart_item_description">
|
|
128
|
+
<strong><%= link_to variant.product.name, spree.product_url(variant.product) %></strong>
|
|
129
|
+
</td>
|
|
130
|
+
</tr>
|
|
131
|
+
<% end %>
|
|
132
|
+
</table>
|
|
133
|
+
</td>
|
|
134
|
+
</tr>
|
|
135
|
+
</table>
|
|
136
|
+
</td>
|
|
137
|
+
</tr>
|
|
138
|
+
</table>
|
|
139
|
+
<br />
|
|
140
|
+
</td>
|
|
141
|
+
</tr>
|
|
142
|
+
</table>
|
|
143
|
+
</center>
|
|
144
|
+
</body>
|
|
145
|
+
</html>
|