spree_klaviyo 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/LICENSE.md +12 -0
- data/README.md +65 -0
- data/Rakefile +21 -0
- data/app/assets/config/spree_klaviyo_manifest.js +1 -0
- data/app/assets/images/integration_icons/klaviyo-logo.png +0 -0
- data/app/clients/spree_klaviyo/klaviyo/client.rb +77 -0
- data/app/controllers/spree_klaviyo/addresses_controller_decorator.rb +18 -0
- data/app/controllers/spree_klaviyo/profile_controller_decorator.rb +18 -0
- data/app/controllers/spree_klaviyo/user_registrations_controller_decorator.rb +22 -0
- data/app/jobs/spree_klaviyo/base_job.rb +5 -0
- data/app/jobs/spree_klaviyo/create_back_in_stock_subscription_job.rb +10 -0
- data/app/jobs/spree_klaviyo/create_event_job.rb +10 -0
- data/app/jobs/spree_klaviyo/create_or_update_profile_job.rb +10 -0
- data/app/jobs/spree_klaviyo/fetch_profile_job.rb +15 -0
- data/app/jobs/spree_klaviyo/subscribe_job.rb +10 -0
- data/app/jobs/spree_klaviyo/unsubscribe_job.rb +10 -0
- data/app/models/concerns/spree_klaviyo/user_methods.rb +24 -0
- data/app/models/spree/integrations/klaviyo.rb +122 -0
- data/app/models/spree_klaviyo/analytics_event_handler.rb +61 -0
- data/app/models/spree_klaviyo/order_decorator.rb +34 -0
- data/app/models/spree_klaviyo/shipment_handler_decorator.rb +25 -0
- data/app/models/spree_klaviyo/user_decorator.rb +9 -0
- data/app/presenters/spree_klaviyo/address_presenter.rb +27 -0
- data/app/presenters/spree_klaviyo/back_in_stock_subscription_presenter.rb +39 -0
- data/app/presenters/spree_klaviyo/event_presenter.rb +97 -0
- data/app/presenters/spree_klaviyo/line_item_presenter.rb +28 -0
- data/app/presenters/spree_klaviyo/order_attributes_presenter.rb +28 -0
- data/app/presenters/spree_klaviyo/order_presenter.rb +95 -0
- data/app/presenters/spree_klaviyo/product_presenter.rb +32 -0
- data/app/presenters/spree_klaviyo/shipment_presenter.rb +53 -0
- data/app/presenters/spree_klaviyo/subscribe_presenter.rb +48 -0
- data/app/presenters/spree_klaviyo/taxon_presenter.rb +19 -0
- data/app/presenters/spree_klaviyo/user_presenter.rb +39 -0
- data/app/services/spree_klaviyo/base.rb +5 -0
- data/app/services/spree_klaviyo/create_event.rb +16 -0
- data/app/services/spree_klaviyo/create_or_update_profile.rb +25 -0
- data/app/services/spree_klaviyo/fetch_profile.rb +15 -0
- data/app/services/spree_klaviyo/subscribe.rb +13 -0
- data/app/services/spree_klaviyo/unsubscribe.rb +13 -0
- data/app/views/spree/admin/integrations/forms/_klaviyo.html.erb +48 -0
- data/config/i18n-tasks.yml +173 -0
- data/config/initializers/spree.rb +4 -0
- data/config/locales/en.yml +11 -0
- data/config/routes.rb +3 -0
- data/lib/generators/spree_klaviyo/install/install_generator.rb +20 -0
- data/lib/spree_klaviyo/configuration.rb +8 -0
- data/lib/spree_klaviyo/engine.rb +28 -0
- data/lib/spree_klaviyo/factories.rb +3 -0
- data/lib/spree_klaviyo/testing_support/factories/klaviyo_integration.rb +9 -0
- data/lib/spree_klaviyo/version.rb +7 -0
- data/lib/spree_klaviyo.rb +11 -0
- metadata +234 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class BackInStockSubscriptionPresenter
|
|
3
|
+
def initialize(email:, variant_id:)
|
|
4
|
+
@email = email
|
|
5
|
+
@variant_id = variant_id
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
{
|
|
10
|
+
data: {
|
|
11
|
+
type: 'back-in-stock-subscription',
|
|
12
|
+
attributes: {
|
|
13
|
+
channels: %w[EMAIL],
|
|
14
|
+
profile: {
|
|
15
|
+
data: {
|
|
16
|
+
type: 'profile',
|
|
17
|
+
attributes: {
|
|
18
|
+
email: email
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
relationships: {
|
|
24
|
+
variant: {
|
|
25
|
+
data: {
|
|
26
|
+
type: 'catalog-variant',
|
|
27
|
+
id: "$custom:::$default:::#{variant_id}"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :email, :variant_id
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class EventPresenter
|
|
3
|
+
def initialize(integration:, event:, resource:, email:, guest_id: nil)
|
|
4
|
+
@integration = integration
|
|
5
|
+
@store = integration.store
|
|
6
|
+
@resource = resource
|
|
7
|
+
@event = event
|
|
8
|
+
@email = email
|
|
9
|
+
@guest_id = guest_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
{
|
|
14
|
+
data: {
|
|
15
|
+
type: 'event',
|
|
16
|
+
attributes: {
|
|
17
|
+
properties: properties,
|
|
18
|
+
metric: {
|
|
19
|
+
data: {
|
|
20
|
+
type: 'metric',
|
|
21
|
+
attributes: {
|
|
22
|
+
name: @event
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
profile: @email.present? ? profile : guest_profile,
|
|
27
|
+
**top_level_attributes
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def top_level_attributes
|
|
36
|
+
if @resource.is_a?(::Spree::Order)
|
|
37
|
+
OrderAttributesPresenter.new(event_name: @event, order: @resource).call
|
|
38
|
+
else
|
|
39
|
+
{}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def properties
|
|
44
|
+
if @resource.is_a? ::Spree::Order
|
|
45
|
+
OrderPresenter.new(order: @resource).call
|
|
46
|
+
elsif @resource.is_a? ::Spree::Shipment
|
|
47
|
+
ShipmentPresenter.new(shipment: @resource, store: @store).call
|
|
48
|
+
elsif @resource.is_a? ::Spree::Product
|
|
49
|
+
ProductPresenter.new(product: @resource, store: @store).call
|
|
50
|
+
elsif @resource.is_a? ::Spree::Taxon
|
|
51
|
+
TaxonPresenter.new(taxon: @resource).call
|
|
52
|
+
elsif @resource.is_a? String
|
|
53
|
+
{
|
|
54
|
+
query: @resource
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
{}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def profile
|
|
62
|
+
if @resource.is_a?(::Spree::Order) && events_that_update_profile.include?(@event)
|
|
63
|
+
UserPresenter.new(
|
|
64
|
+
email: @email,
|
|
65
|
+
address: @resource&.bill_address || @resource&.ship_address,
|
|
66
|
+
user: @resource&.user,
|
|
67
|
+
guest_id: @guest_id
|
|
68
|
+
).call
|
|
69
|
+
else
|
|
70
|
+
{
|
|
71
|
+
data: {
|
|
72
|
+
type: 'profile',
|
|
73
|
+
attributes: {
|
|
74
|
+
anonymous_id: @guest_id,
|
|
75
|
+
email: @email
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def guest_profile
|
|
83
|
+
{
|
|
84
|
+
data: {
|
|
85
|
+
type: 'profile',
|
|
86
|
+
attributes: {
|
|
87
|
+
anonymous_id: @guest_id
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def events_that_update_profile
|
|
94
|
+
@events_that_update_profile ||= [::Spree::Analytics.events[:checkout_email_entered], ::Spree::Analytics.events[:order_completed]]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class LineItemPresenter < ProductPresenter
|
|
3
|
+
include ::Spree::BaseHelper
|
|
4
|
+
include ::Spree::ProductsHelper
|
|
5
|
+
|
|
6
|
+
def initialize(resource:, quantity:, total_price:, currency:, store:, position: nil)
|
|
7
|
+
@resource = resource
|
|
8
|
+
@position = position
|
|
9
|
+
@quantity = quantity
|
|
10
|
+
@total_price = total_price
|
|
11
|
+
super(product: resource&.product, store: store)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
return {} if @resource.nil?
|
|
16
|
+
|
|
17
|
+
super.merge({
|
|
18
|
+
quantity: @quantity,
|
|
19
|
+
item_price: @resource.price,
|
|
20
|
+
row_total: @total_price
|
|
21
|
+
})
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :resource, :quantity, :total_price, :currency, :position, :current_store, :feeable_item_ids
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class OrderAttributesPresenter
|
|
3
|
+
def initialize(order:, event_name:)
|
|
4
|
+
@order = order
|
|
5
|
+
@event_name = event_name
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
{
|
|
10
|
+
value: @order.total&.to_f,
|
|
11
|
+
time: time(@order)&.iso8601
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def time(order)
|
|
18
|
+
case @event_name
|
|
19
|
+
when ::Spree::Analytics.events[:order_completed]
|
|
20
|
+
order.completed_at
|
|
21
|
+
when 'Order Cancelled'
|
|
22
|
+
order.canceled_at
|
|
23
|
+
else
|
|
24
|
+
order.updated_at
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class OrderPresenter
|
|
3
|
+
include ::Spree::BaseHelper
|
|
4
|
+
include ::Spree::ImagesHelper
|
|
5
|
+
|
|
6
|
+
def initialize(order:)
|
|
7
|
+
@order = order
|
|
8
|
+
@current_store = order.store
|
|
9
|
+
@products = products(order)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
{
|
|
14
|
+
email: @order&.user&.email || @order.email,
|
|
15
|
+
customer_name: @order.name || ::Spree.t('customer'),
|
|
16
|
+
store_name: @current_store.name,
|
|
17
|
+
order_id: @order.number,
|
|
18
|
+
order_number: @order.number,
|
|
19
|
+
affiliation: @order.store.name,
|
|
20
|
+
value: subtotal,
|
|
21
|
+
subtotal: subtotal,
|
|
22
|
+
item_total: @order.item_total&.to_f,
|
|
23
|
+
total: @order.total.to_f,
|
|
24
|
+
revenue: @order.total&.to_f,
|
|
25
|
+
shipping: @order.shipments.sum(&:cost).to_f,
|
|
26
|
+
shipping_method: shipping_method_names,
|
|
27
|
+
tax: @order.additional_tax_total&.to_f,
|
|
28
|
+
included_tax: @order.included_tax_total&.to_f,
|
|
29
|
+
discount: @order.promo_total&.to_f,
|
|
30
|
+
items: products(@order),
|
|
31
|
+
coupon: @order.coupon_code.to_s,
|
|
32
|
+
currency: @order.currency,
|
|
33
|
+
completed_at: @order.completed_at&.iso8601.to_s,
|
|
34
|
+
checkout_url: ::Spree::Core::Engine.routes.url_helpers.checkout_url(host: @current_store.url_or_custom_domain, token: @order.token),
|
|
35
|
+
all_adjustments: all_adjustments,
|
|
36
|
+
bill_address: AddressPresenter.new(address: @order.bill_address).call,
|
|
37
|
+
ship_address: AddressPresenter.new(address: @order.ship_address).call
|
|
38
|
+
}.merge(try_shipped_shipments)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :order, :current_store
|
|
44
|
+
|
|
45
|
+
def products(order)
|
|
46
|
+
@products ||= order.line_items.includes(variant: { product: :taxons }).map.with_index do |line_item, index|
|
|
47
|
+
LineItemPresenter.new(
|
|
48
|
+
resource: line_item,
|
|
49
|
+
quantity: line_item.quantity,
|
|
50
|
+
total_price: line_item.amount,
|
|
51
|
+
currency: order.currency,
|
|
52
|
+
position: index + 1,
|
|
53
|
+
store: order.store
|
|
54
|
+
).call
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def all_adjustments
|
|
59
|
+
@order.all_adjustments.promotion.eligible.group_by(&:label).map do |label, adjustments|
|
|
60
|
+
{
|
|
61
|
+
label: label,
|
|
62
|
+
amount: adjustments.sum(&:amount).to_f
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def shipping_method_names
|
|
68
|
+
@shipping_method_names ||= @order.shipments.map { |shipment| shipping_method_name(shipment) }.compact.uniq.join(',')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def shipping_method_name(shipment)
|
|
72
|
+
shipment.shipping_method&.name.to_s
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def try_shipped_shipments
|
|
76
|
+
return {} unless @order.fully_shipped? && @order.shipments.shipped.any?
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
shipped_shipments: @order.shipments.shipped.map do |shipment|
|
|
80
|
+
ShipmentPresenter.new(order: @order, shipment: shipment).call
|
|
81
|
+
end
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def subtotal
|
|
86
|
+
@order.analytics_subtotal
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def try_variants(variant)
|
|
90
|
+
{
|
|
91
|
+
variant_dict: ::Spree::Variants::OptionsPresenter.new(variant).to_hash
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class ProductPresenter
|
|
3
|
+
include ::Spree::BaseHelper
|
|
4
|
+
include ::Spree::ImagesHelper
|
|
5
|
+
include Rails.application.routes.mounted_helpers
|
|
6
|
+
|
|
7
|
+
def initialize(product:, store:)
|
|
8
|
+
@product = product
|
|
9
|
+
@current_store = store
|
|
10
|
+
@current_currency = store.default_currency
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
return {} if @product.nil?
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
name: @product.name,
|
|
18
|
+
price: @product.amount_in(current_currency)&.to_f,
|
|
19
|
+
brand: @product&.brand_name,
|
|
20
|
+
category: @product.main_taxon&.pretty_name,
|
|
21
|
+
currency: current_currency,
|
|
22
|
+
url: spree_storefront_resource_url(@product, store: @store),
|
|
23
|
+
image_url: @product.default_image.present? ? spree_image_url(@product.default_image, width: 1200, height: 1200) : '',
|
|
24
|
+
sku: @product.sku
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :current_store, :current_currency
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class ShipmentPresenter < OrderPresenter
|
|
3
|
+
include ::Spree::BaseHelper
|
|
4
|
+
include ::Spree::ProductsHelper
|
|
5
|
+
include ::Spree::ImagesHelper
|
|
6
|
+
include Rails.application.routes.mounted_helpers
|
|
7
|
+
|
|
8
|
+
def initialize(shipment:, order: nil, store: nil)
|
|
9
|
+
@shipment = shipment
|
|
10
|
+
@order = order || shipment.order
|
|
11
|
+
@current_store = store || order.store || shipment.store
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
{
|
|
16
|
+
customer_name: @order.name || ::Spree.t('customer'),
|
|
17
|
+
email: @order&.user&.email || @order.email,
|
|
18
|
+
order_number: @order.number,
|
|
19
|
+
shipping_method: shipping_method_name(@shipment),
|
|
20
|
+
tracking: @shipment&.tracking.to_s,
|
|
21
|
+
tracking_url: @shipment&.tracking_url,
|
|
22
|
+
store_name: @current_store.name,
|
|
23
|
+
cost: @shipment.final_price.to_f,
|
|
24
|
+
completed_at: @order.completed_at&.iso8601.to_s,
|
|
25
|
+
shipped_items: shipped_items,
|
|
26
|
+
bill_address: AddressPresenter.new(address: @order.bill_address).call,
|
|
27
|
+
ship_address: AddressPresenter.new(address: @order.ship_address).call
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
attr_reader :order, :shipment, :current_store
|
|
34
|
+
|
|
35
|
+
def shipped_items
|
|
36
|
+
@shipment.manifest.map do |shipped_item|
|
|
37
|
+
shipped_items_quantity = shipped_item.line_item.quantity
|
|
38
|
+
{
|
|
39
|
+
url: spree_storefront_resource_url(shipped_item.variant.product),
|
|
40
|
+
image_url: shipped_item.variant.default_image.present? ? spree_image_url(shipped_item.variant.default_image, width: 1200, height: 1200) : '',
|
|
41
|
+
name: shipped_item.variant.name,
|
|
42
|
+
variant: shipped_item.variant.options_text,
|
|
43
|
+
sku: shipped_item.variant.sku,
|
|
44
|
+
shipped_quantity: shipped_item.quantity,
|
|
45
|
+
total_quantity: shipped_items_quantity,
|
|
46
|
+
price: shipped_item.line_item.price.to_f,
|
|
47
|
+
total_price: shipped_item.line_item.amount.to_f,
|
|
48
|
+
brand: brand_name(shipped_item.variant.product)
|
|
49
|
+
}.merge(try_variants(shipped_item.variant))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class SubscribePresenter
|
|
3
|
+
SUBSCRIBED = 'SUBSCRIBED'.freeze
|
|
4
|
+
UNSUBSCRIBED = 'UNSUBSCRIBED'.freeze
|
|
5
|
+
|
|
6
|
+
def initialize(email:, list_id:, type: 'profile-subscription-bulk-create-job', subscribed: true)
|
|
7
|
+
@email = email
|
|
8
|
+
@list_id = list_id
|
|
9
|
+
@type = type
|
|
10
|
+
@subscribed = subscribed
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
{
|
|
15
|
+
data: {
|
|
16
|
+
type: @type,
|
|
17
|
+
attributes: {
|
|
18
|
+
profiles: {
|
|
19
|
+
data: [
|
|
20
|
+
{
|
|
21
|
+
"type": 'profile',
|
|
22
|
+
"attributes": {
|
|
23
|
+
email: @email,
|
|
24
|
+
"subscriptions": {
|
|
25
|
+
"email": {
|
|
26
|
+
"marketing": {
|
|
27
|
+
"consent": @subscribed ? SUBSCRIBED : UNSUBSCRIBED
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
relationships: {
|
|
37
|
+
list: {
|
|
38
|
+
data: {
|
|
39
|
+
type: 'list',
|
|
40
|
+
id: @list_id
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class TaxonPresenter
|
|
3
|
+
include ::Spree::BaseHelper
|
|
4
|
+
include ::Spree::ImagesHelper
|
|
5
|
+
include Rails.application.routes.mounted_helpers
|
|
6
|
+
|
|
7
|
+
def initialize(taxon:)
|
|
8
|
+
@taxon = taxon
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
{
|
|
13
|
+
name: @taxon.pretty_name,
|
|
14
|
+
image_url: @taxon.image.present? ? spree_image_url(@taxon.image, width: 1200, height: 1200) : '',
|
|
15
|
+
url: spree_storefront_resource_url(@taxon, store: @taxon.store)
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class UserPresenter
|
|
3
|
+
def initialize(email:, address: nil, user: nil, guest_id: nil)
|
|
4
|
+
@email = email
|
|
5
|
+
@address = address
|
|
6
|
+
@user = user
|
|
7
|
+
@guest_id = guest_id
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
{
|
|
12
|
+
data: {
|
|
13
|
+
type: 'profile',
|
|
14
|
+
attributes: {
|
|
15
|
+
anonymous_id: @guest_id,
|
|
16
|
+
email: @user.present? ? @user.email : @email,
|
|
17
|
+
first_name: @user&.first_name || @address&.first_name,
|
|
18
|
+
last_name: @user&.last_name || @address&.last_name,
|
|
19
|
+
external_id: @user&.id,
|
|
20
|
+
location: {
|
|
21
|
+
address1: @address&.address1,
|
|
22
|
+
address2: @address&.address2,
|
|
23
|
+
city: @address&.city,
|
|
24
|
+
country: @address&.country_name,
|
|
25
|
+
region: @address&.state_text,
|
|
26
|
+
zip: @address&.zipcode
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}.merge(try_klaviyo_id)
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def try_klaviyo_id
|
|
36
|
+
@user&.klaviyo_id.present? ? { id: @user.klaviyo_id } : {}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class CreateEvent < Base
|
|
3
|
+
prepend ::Spree::ServiceModule::Base
|
|
4
|
+
|
|
5
|
+
def call(klaviyo_integration:, event:, resource:, email:, guest_id: nil)
|
|
6
|
+
return failure(false, ::Spree.t('admin.integrations.klaviyo.not_found')) unless klaviyo_integration
|
|
7
|
+
|
|
8
|
+
klaviyo_integration.create_event(
|
|
9
|
+
event: event,
|
|
10
|
+
resource: resource,
|
|
11
|
+
email: email,
|
|
12
|
+
guest_id: guest_id
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class CreateOrUpdateProfile < Base
|
|
3
|
+
prepend ::Spree::ServiceModule::Base
|
|
4
|
+
|
|
5
|
+
def call(klaviyo_integration:, user:, guest_id: nil)
|
|
6
|
+
return failure(false, ::Spree.t('admin.integrations.klaviyo.not_found')) unless klaviyo_integration
|
|
7
|
+
|
|
8
|
+
if user.klaviyo_id.blank?
|
|
9
|
+
fetch_profile_result = FetchProfile.call(klaviyo_integration: klaviyo_integration, user: user)
|
|
10
|
+
|
|
11
|
+
if fetch_profile_result.success?
|
|
12
|
+
return klaviyo_integration.update_profile(user, guest_id) if guest_id.present?
|
|
13
|
+
|
|
14
|
+
return fetch_profile_result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
klaviyo_integration.create_profile(user).tap do |result|
|
|
18
|
+
user.update!(klaviyo_id: JSON.parse(result.value).dig('data', 'id')) if result.success?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
klaviyo_integration.update_profile(user)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class FetchProfile < Base
|
|
3
|
+
prepend ::Spree::ServiceModule::Base
|
|
4
|
+
|
|
5
|
+
def call(klaviyo_integration:, user:)
|
|
6
|
+
return failure(false, ::Spree.t('admin.integrations.klaviyo.not_found')) unless klaviyo_integration
|
|
7
|
+
|
|
8
|
+
return success(user.klaviyo_id) if user.klaviyo_id.present?
|
|
9
|
+
|
|
10
|
+
klaviyo_integration.fetch_profile(email: user.email).tap do |result|
|
|
11
|
+
user.update!(klaviyo_id: JSON.parse(result.value)['data'].first['id']) if result.success?
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class Subscribe < Base
|
|
3
|
+
prepend ::Spree::ServiceModule::Base
|
|
4
|
+
|
|
5
|
+
def call(klaviyo_integration:, email:, user: nil)
|
|
6
|
+
return failure(false, ::Spree.t('admin.integrations.klaviyo.not_found')) unless klaviyo_integration
|
|
7
|
+
|
|
8
|
+
klaviyo_integration.subscribe_user(email).tap do |result|
|
|
9
|
+
user.update(klaviyo_subscribed: true) if result.success? && user && !user.klaviyo_subscribed?
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module SpreeKlaviyo
|
|
2
|
+
class Unsubscribe < Base
|
|
3
|
+
prepend ::Spree::ServiceModule::Base
|
|
4
|
+
|
|
5
|
+
def call(klaviyo_integration:, email:, user: nil)
|
|
6
|
+
return failure(false, ::Spree.t('admin.integrations.klaviyo.not_found')) unless klaviyo_integration
|
|
7
|
+
|
|
8
|
+
klaviyo_integration.unsubscribe_user(email).tap do |result|
|
|
9
|
+
user.update(klaviyo_subscribed: false) if result.success? && user && user.klaviyo_subscribed?
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<div class="alert alert-info">
|
|
2
|
+
<div>
|
|
3
|
+
Having trouble? Follow <%= external_link_to " our setup guide", "https://spreecommerce.org/docs/integrations/marketing/klaviyo" %> for more information.
|
|
4
|
+
</div>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="col-12 col-md-6 p-0">
|
|
8
|
+
<div class="card mb-4">
|
|
9
|
+
<div class="card-body">
|
|
10
|
+
<%= preference_field(
|
|
11
|
+
@integration,
|
|
12
|
+
form,
|
|
13
|
+
"klaviyo_public_api_key",
|
|
14
|
+
i18n_scope: "admin.integrations.klaviyo",
|
|
15
|
+
) %>
|
|
16
|
+
<p class="mb-4 mt-n2">
|
|
17
|
+
<%= external_link_to "Where to find my API Key?",
|
|
18
|
+
"https://help.klaviyo.com/hc/en-us/articles/115005062267-How-to-Manage-Your-Account-s-API-Keys" %>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<%= preference_field(
|
|
22
|
+
@integration,
|
|
23
|
+
form,
|
|
24
|
+
"klaviyo_private_api_key",
|
|
25
|
+
i18n_scope: "admin.integrations.klaviyo",
|
|
26
|
+
) %>
|
|
27
|
+
<p class="mb-4 mt-n2">
|
|
28
|
+
<%= external_link_to "Where to find my Private API key?",
|
|
29
|
+
"https://help.klaviyo.com/hc/en-us/articles/115005062267-How-to-Manage-Your-Account-s-API-Keys" %>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<%= preference_field(
|
|
33
|
+
@integration,
|
|
34
|
+
form,
|
|
35
|
+
"default_newsletter_list_id",
|
|
36
|
+
i18n_scope: "admin.integrations.klaviyo",
|
|
37
|
+
) %>
|
|
38
|
+
<p class="mt-n2">
|
|
39
|
+
<%= external_link_to "Where to find list ID?",
|
|
40
|
+
"https://help.klaviyo.com/hc/en-us/articles/115005078647-How-to-find-a-list-ID" %>
|
|
41
|
+
</p>
|
|
42
|
+
<div class="alert alert-info mt-n2 mb-0">
|
|
43
|
+
This list will be used as the default newsletter list for your
|
|
44
|
+
customers signing up from storefront and checkout.
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|