spree_gladly 1.0.0
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/.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
|