spree_gladly 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +45 -0
- data/.travis.yml +69 -0
- data/Appraisals +63 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/Gemfile +7 -0
- data/LICENSE +11 -0
- data/README.md +473 -0
- data/Rakefile +23 -0
- data/app/concerns/customer/database_adapter.rb +21 -0
- data/app/controllers/application_controller.rb +2 -0
- data/app/controllers/spree/admin/gladly_settings_controller.rb +38 -0
- data/app/controllers/spree/api/v1/customers_controller.rb +60 -0
- data/app/finders/customer/base_lookup.rb +37 -0
- data/app/finders/customer/basic_lookup.rb +26 -0
- data/app/finders/customer/detailed_lookup.rb +19 -0
- data/app/finders/customer/guest/basic_finder.rb +37 -0
- data/app/finders/customer/guest/detailed_finder.rb +42 -0
- data/app/finders/customer/registered/basic_finder.rb +68 -0
- data/app/finders/customer/registered/detailed_finder.rb +43 -0
- data/app/models/spree_gladly/configuration.rb +25 -0
- data/app/overrides/add_gladly_admin_menu_links.rb +10 -0
- data/app/presenters/customer/address_presenter.rb +27 -0
- data/app/presenters/customer/basic_lookup_presenter.rb +29 -0
- data/app/presenters/customer/detailed_lookup_presenter.rb +30 -0
- data/app/presenters/customer/guest/basic_presenter.rb +53 -0
- data/app/presenters/customer/guest/detailed_presenter.rb +117 -0
- data/app/presenters/customer/registered/basic_presenter.rb +60 -0
- data/app/presenters/customer/registered/detailed_presenter.rb +137 -0
- data/app/services/auth/authorization_header.rb +35 -0
- data/app/services/auth/error.rb +4 -0
- data/app/services/auth/header_parse_error.rb +4 -0
- data/app/services/auth/invalid_signature_error.rb +4 -0
- data/app/services/auth/missing_key_error.rb +4 -0
- data/app/services/auth/request_normalizer.rb +37 -0
- data/app/services/auth/signature_validator.rb +68 -0
- data/app/services/auth/time_header.rb +25 -0
- data/app/validators/lookup_validator.rb +89 -0
- data/app/validators/validation_result.rb +25 -0
- data/app/views/spree/admin/gladly_settings/edit.html.erb +25 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +25 -0
- data/config/routes.rb +13 -0
- data/gemfiles/spree_3_0.gemfile +16 -0
- data/gemfiles/spree_3_1.gemfile +16 -0
- data/gemfiles/spree_3_7.gemfile +11 -0
- data/gemfiles/spree_4_0.gemfile +11 -0
- data/gemfiles/spree_4_1.gemfile +11 -0
- data/gemfiles/spree_4_2.gemfile +11 -0
- data/gemfiles/spree_master.gemfile +11 -0
- data/lib/generators/spree_gladly/install/install_generator.rb +17 -0
- data/lib/generators/spree_gladly/install/templates/config/initializers/spree_gladly.rb +18 -0
- data/lib/spree_gladly.rb +13 -0
- data/lib/spree_gladly/engine.rb +25 -0
- data/lib/spree_gladly/factories.rb +9 -0
- data/lib/spree_gladly/version.rb +5 -0
- data/spree.png +0 -0
- data/spree_gladly.gemspec +35 -0
- metadata +201 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
# XXX: frozen_string_literal comment breaks on Spree 3.0
|
2
|
+
|
3
|
+
Deface::Override.new(
|
4
|
+
virtual_path: 'spree/admin/shared/sub_menu/_configuration',
|
5
|
+
name: 'add_gladly_admin_menu_links',
|
6
|
+
insert_bottom: "ul[data-hook='admin_configurations_sidebar_menu']",
|
7
|
+
# rubocop:disable Layout/LineLength
|
8
|
+
text: "<%= configurations_sidebar_menu_item(Spree.t('spree_gladly.settings'), edit_admin_gladly_settings_path) if can? :manage, Spree::Config %>"
|
9
|
+
# rubocop:enable Layout/LineLength
|
10
|
+
)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Customer
|
2
|
+
class AddressPresenter
|
3
|
+
def initialize(address)
|
4
|
+
@address = address
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
return nil if address.nil?
|
9
|
+
|
10
|
+
components = [
|
11
|
+
address.address1,
|
12
|
+
address.address2,
|
13
|
+
address.city,
|
14
|
+
address.state_text,
|
15
|
+
address.zipcode,
|
16
|
+
address.country.to_s
|
17
|
+
]
|
18
|
+
|
19
|
+
components.reject(&:blank?)
|
20
|
+
.join(', ')
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :address
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
class BasicLookupPresenter
|
5
|
+
def initialize(resource:)
|
6
|
+
@resource = resource
|
7
|
+
@registered_customers = resource.registered_customers
|
8
|
+
@guest_customers = resource.guest_customers
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_h
|
12
|
+
return [] if guest_customers.empty? && registered_customers.empty?
|
13
|
+
|
14
|
+
registered_presenter.concat(guest_presenter)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :resource, :registered_customers, :guest_customers
|
20
|
+
|
21
|
+
def registered_presenter
|
22
|
+
Customer::Registered::BasicPresenter.new(resource: registered_customers).to_h
|
23
|
+
end
|
24
|
+
|
25
|
+
def guest_presenter
|
26
|
+
Customer::Guest::BasicPresenter.new(resource: guest_customers).to_h
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
class DetailedLookupPresenter
|
5
|
+
include Spree::Core::Engine.routes.url_helpers
|
6
|
+
include Spree::BaseHelper
|
7
|
+
|
8
|
+
def initialize(resource:)
|
9
|
+
@resource = resource
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
return [] unless resource.customer.present?
|
14
|
+
|
15
|
+
resource.guest ? guest_presenter : registered_presenter
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :resource
|
21
|
+
|
22
|
+
def registered_presenter
|
23
|
+
Customer::Registered::DetailedPresenter.new(resource: resource).to_h
|
24
|
+
end
|
25
|
+
|
26
|
+
def guest_presenter
|
27
|
+
Customer::Guest::DetailedPresenter.new(resource: resource).to_h
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module Guest
|
5
|
+
class BasicPresenter
|
6
|
+
def initialize(resource:)
|
7
|
+
@resource = resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_h
|
11
|
+
return [] if resource.empty?
|
12
|
+
|
13
|
+
basic_payload
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :resource
|
19
|
+
|
20
|
+
def basic_payload
|
21
|
+
resource.map do |guest_customer|
|
22
|
+
{
|
23
|
+
externalCustomerId: guest_customer.email,
|
24
|
+
address: formatted_address(guest_customer),
|
25
|
+
name: guest_customer&.bill_address&.full_name.to_s,
|
26
|
+
emails: customer_emails(guest_customer),
|
27
|
+
phones: customer_phones(guest_customer)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def customer_emails(guest_customer)
|
33
|
+
[
|
34
|
+
{
|
35
|
+
original: guest_customer.email
|
36
|
+
}
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
def customer_phones(guest_customer)
|
41
|
+
[
|
42
|
+
{
|
43
|
+
original: guest_customer&.bill_address&.phone.to_s
|
44
|
+
}
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
def formatted_address(guest_customer)
|
49
|
+
Customer::AddressPresenter.new(guest_customer&.bill_address).to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module Guest
|
5
|
+
class DetailedPresenter
|
6
|
+
include Spree::Core::Engine.routes.url_helpers
|
7
|
+
include Spree::BaseHelper
|
8
|
+
|
9
|
+
def initialize(resource:)
|
10
|
+
@resource = resource
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
return [] unless resource.customer.present?
|
15
|
+
|
16
|
+
detailed_payload
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :resource
|
22
|
+
|
23
|
+
def detailed_payload
|
24
|
+
[
|
25
|
+
{
|
26
|
+
externalCustomerId: resource.customer.email,
|
27
|
+
customAttributes: custom_attributes,
|
28
|
+
emails: emails,
|
29
|
+
transactions: transactions
|
30
|
+
}
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
def custom_attributes
|
35
|
+
{
|
36
|
+
totalOrderCount: transactions_size,
|
37
|
+
guestOrderCount: transactions_size,
|
38
|
+
lifetimeValue: lifetime_value
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def transactions
|
43
|
+
resource.transactions.map do |transaction|
|
44
|
+
{
|
45
|
+
type: 'ORDER',
|
46
|
+
orderStatus: transaction.state,
|
47
|
+
orderNumber: transaction.number,
|
48
|
+
guest: 'yes',
|
49
|
+
products: transaction_products(transaction: transaction),
|
50
|
+
orderLink: order_url(transaction),
|
51
|
+
note: transaction&.special_instructions.to_s,
|
52
|
+
orderTotal: Spree::Money.new(transaction.total).to_html,
|
53
|
+
createdAt: transaction.created_at
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def transaction_products(transaction:)
|
59
|
+
transaction.line_items.map do |item|
|
60
|
+
{
|
61
|
+
name: item.variant.name,
|
62
|
+
status: item_status(item: item),
|
63
|
+
sku: item.variant.sku,
|
64
|
+
quantity: item.quantity.to_s,
|
65
|
+
total: Spree::Money.new(item.total).to_html,
|
66
|
+
unitPrice: Spree::Money.new(item.price).to_html,
|
67
|
+
imageUrl: item_image_url(item: item)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def transactions_size
|
73
|
+
@transactions_size ||= resource.transactions.size.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def lifetime_value
|
77
|
+
return '0' if resource.transactions.empty?
|
78
|
+
|
79
|
+
Spree::Money.new(resource.transactions.sum(&:total)).to_html
|
80
|
+
end
|
81
|
+
|
82
|
+
def order_url(transaction)
|
83
|
+
edit_admin_order_url(id: transaction.number, host: Rails.application.routes.default_url_options[:host])
|
84
|
+
end
|
85
|
+
|
86
|
+
def item_image_url(item:)
|
87
|
+
return '' if item.product.images.empty?
|
88
|
+
|
89
|
+
item.product.images.first&.attachment&.url
|
90
|
+
end
|
91
|
+
|
92
|
+
def item_status(item:)
|
93
|
+
item.sufficient_stock? ? 'fulfilled' : 'cancelled'
|
94
|
+
end
|
95
|
+
|
96
|
+
def emails
|
97
|
+
[
|
98
|
+
{
|
99
|
+
original: resource.customer.email
|
100
|
+
}
|
101
|
+
]
|
102
|
+
end
|
103
|
+
|
104
|
+
def phones
|
105
|
+
[
|
106
|
+
{
|
107
|
+
original: address&.phone
|
108
|
+
}
|
109
|
+
]
|
110
|
+
end
|
111
|
+
|
112
|
+
def address
|
113
|
+
@address ||= resource.customer.bill_address
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module Registered
|
5
|
+
class BasicPresenter
|
6
|
+
def initialize(resource:)
|
7
|
+
@resource = resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_h
|
11
|
+
return [] if resource.empty?
|
12
|
+
|
13
|
+
basic_payload
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :resource
|
19
|
+
|
20
|
+
def basic_payload
|
21
|
+
resource.map do |user|
|
22
|
+
{
|
23
|
+
externalCustomerId: user.email,
|
24
|
+
customAttributes: {
|
25
|
+
spreeId: user.id
|
26
|
+
},
|
27
|
+
address: formatted_address(user),
|
28
|
+
name: address(user)&.full_name.to_s,
|
29
|
+
emails: customer_emails(user),
|
30
|
+
phones: customer_phones(user)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def customer_emails(user)
|
36
|
+
[
|
37
|
+
{
|
38
|
+
original: user.email
|
39
|
+
}
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def customer_phones(user)
|
44
|
+
[
|
45
|
+
{
|
46
|
+
original: address(user)&.phone.to_s
|
47
|
+
}
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
def address(user)
|
52
|
+
user.bill_address || user.orders.first&.bill_address
|
53
|
+
end
|
54
|
+
|
55
|
+
def formatted_address(user)
|
56
|
+
Customer::AddressPresenter.new(address(user)).to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/ClassLength
|
4
|
+
module Customer
|
5
|
+
module Registered
|
6
|
+
class DetailedPresenter
|
7
|
+
include Spree::Core::Engine.routes.url_helpers
|
8
|
+
include Spree::BaseHelper
|
9
|
+
|
10
|
+
def initialize(resource:)
|
11
|
+
@resource = resource
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
return [] unless resource.customer.present?
|
16
|
+
|
17
|
+
detailed_payload
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :resource
|
23
|
+
|
24
|
+
def detailed_payload
|
25
|
+
[
|
26
|
+
{
|
27
|
+
externalCustomerId: resource.customer.email,
|
28
|
+
name: address&.full_name,
|
29
|
+
address: formatted_address,
|
30
|
+
emails: emails,
|
31
|
+
phones: phones,
|
32
|
+
customAttributes: custom_attributes,
|
33
|
+
transactions: transactions
|
34
|
+
}
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
def custom_attributes
|
39
|
+
{
|
40
|
+
spreeId: resource.customer.id,
|
41
|
+
lifetimeValue: lifetime_value,
|
42
|
+
totalOrderCount: resource.transactions.size.to_s,
|
43
|
+
guestOrderCount: calculate_guest_transactions.to_s,
|
44
|
+
memberSince: resource.customer.created_at,
|
45
|
+
customerLink: customer_profile_url(resource.customer)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def transactions
|
50
|
+
resource.transactions.map do |transaction|
|
51
|
+
{
|
52
|
+
type: 'ORDER',
|
53
|
+
orderStatus: transaction.state,
|
54
|
+
orderNumber: transaction.number,
|
55
|
+
guest: transaction_type(transaction),
|
56
|
+
products: transaction_products(transaction: transaction),
|
57
|
+
orderLink: order_url(transaction),
|
58
|
+
note: transaction&.special_instructions.to_s,
|
59
|
+
orderTotal: Spree::Money.new(transaction.total).to_html,
|
60
|
+
createdAt: transaction.created_at
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def transaction_products(transaction:)
|
66
|
+
transaction.line_items.map do |item|
|
67
|
+
{
|
68
|
+
name: item.variant.name,
|
69
|
+
status: item_status(item: item),
|
70
|
+
sku: item.variant.sku,
|
71
|
+
quantity: item.quantity.to_s,
|
72
|
+
total: Spree::Money.new(item.total).to_html,
|
73
|
+
unitPrice: Spree::Money.new(item.price).to_html,
|
74
|
+
imageUrl: item_image_url(item: item)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def calculate_guest_transactions
|
80
|
+
resource.transactions.select { |item| item.user_id.nil? }.size
|
81
|
+
end
|
82
|
+
|
83
|
+
def transaction_type(order)
|
84
|
+
order.user_id.nil? ? 'yes' : 'no'
|
85
|
+
end
|
86
|
+
|
87
|
+
def customer_profile_url(customer)
|
88
|
+
edit_admin_user_url(id: customer.id, host: Rails.application.routes.default_url_options[:host])
|
89
|
+
end
|
90
|
+
|
91
|
+
def order_url(transaction)
|
92
|
+
edit_admin_order_url(id: transaction.number, host: Rails.application.routes.default_url_options[:host])
|
93
|
+
end
|
94
|
+
|
95
|
+
def lifetime_value
|
96
|
+
return '0' if resource.transactions.empty?
|
97
|
+
|
98
|
+
Spree::Money.new(resource.transactions.sum(&:total)).to_html
|
99
|
+
end
|
100
|
+
|
101
|
+
def item_image_url(item:)
|
102
|
+
return '' if item.product.images.empty?
|
103
|
+
|
104
|
+
item.product.images.first&.attachment&.url
|
105
|
+
end
|
106
|
+
|
107
|
+
def item_status(item:)
|
108
|
+
item.sufficient_stock? ? 'fulfilled' : 'cancelled'
|
109
|
+
end
|
110
|
+
|
111
|
+
def emails
|
112
|
+
[
|
113
|
+
{
|
114
|
+
original: resource.customer.email
|
115
|
+
}
|
116
|
+
]
|
117
|
+
end
|
118
|
+
|
119
|
+
def phones
|
120
|
+
[
|
121
|
+
{
|
122
|
+
original: address&.phone
|
123
|
+
}
|
124
|
+
]
|
125
|
+
end
|
126
|
+
|
127
|
+
def address
|
128
|
+
@address ||= resource.customer.bill_address || resource.transactions.first.bill_address
|
129
|
+
end
|
130
|
+
|
131
|
+
def formatted_address
|
132
|
+
Customer::AddressPresenter.new(address).to_s
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
# rubocop:enable Metrics/ClassLength
|