solidus_meta 1.1.1 → 2.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 +4 -4
- data/README.md +50 -9
- data/app/controllers/concerns/solidus_meta/controller_helpers.rb +51 -1
- data/app/helpers/solidus_meta/pixel_helper.rb +20 -0
- data/app/jobs/solidus_meta/base_job.rb +1 -1
- data/app/models/solidus_meta/client.rb +4 -0
- data/app/models/solidus_meta/event.rb +93 -69
- data/app/models/solidus_meta/user_data.rb +79 -0
- data/lib/solidus_meta/configuration.rb +5 -1
- data/lib/solidus_meta/version.rb +1 -1
- data/lib/solidus_meta.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 14bb27e1abde5b941ef688e94da4a4b8649d96eca6fc89a5c85425ee6641b6b8
|
|
4
|
+
data.tar.gz: 76c4b9d5798e65d063ef2047837f629647c3865fb513e23f986794e0c755daf2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ba7f2f85a1fedb47d3fcb66da1b3190f6d210315c922888b50147418c8490292ef990ffcc814719e000f13f6907c8c9f56710dd29383a730839c60f8b638fe4
|
|
7
|
+
data.tar.gz: 896b524d0088eeae20c0364743b8f3afda163f465129abbefbbed7a823e768cd09aaf6a113ff3926cc4047a692ce385d35597d1f49399241855eb711480c4024
|
data/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# SolidusMeta
|
|
2
2
|
|
|
3
|
-
Envia eventos
|
|
3
|
+
Envia eventos para a Meta a partir de uma loja Solidus, pela [Conversions API](https://developers.facebook.com/docs/marketing-api/conversions-api) (servidor) e pelo Pixel (navegador): `ViewContent`, `AddToCart`, `InitiateCheckout`, `AddPaymentInfo`, `Purchase`, `CompleteRegistration` e `Search`.
|
|
4
|
+
|
|
5
|
+
## Por que enviar pelo navegador e pelo servidor
|
|
6
|
+
|
|
7
|
+
Cada lado tem informações que o outro não tem, e a Meta atribui melhor as conversões quando recebe os dois:
|
|
8
|
+
|
|
9
|
+
- **Navegador (Pixel):** roda no navegador do cliente e fala direto com a Meta, que reconhece a pessoa pela sessão do Facebook/Instagram, principalmente no navegador interno desses apps, de onde vem boa parte do tráfego de anúncio no celular. Também identifica visitantes anônimos, que ainda não informaram email ou telefone.
|
|
10
|
+
- **Servidor (Conversions API):** tem os dados do pedido (email, telefone, endereço), não é afetado por bloqueador de anúncio nem pelos limites de cookie do Safari, e envia mesmo quando o cliente já saiu da página, como num PIX confirmado depois por um job.
|
|
11
|
+
|
|
12
|
+
Os dois são enviados com o mesmo `event_id`, então a Meta junta os dois numa conversão só, sem contar duas vezes. Isso só funciona dentro de 48 horas entre um envio e outro, por isso o `Purchase` no navegador só dispara para pedidos completados no último dia.
|
|
13
|
+
|
|
14
|
+
No Gerenciador de Eventos, a "cobertura de eventos" mede a parte dos eventos que chega pelos dois lados, e a cobertura de `event_id` mede quantos deles podem ser desduplicados. A Meta recomenda ao menos 75%.
|
|
4
15
|
|
|
5
16
|
## Instalação
|
|
6
17
|
|
|
@@ -18,14 +29,30 @@ SolidusMeta.configure do |config|
|
|
|
18
29
|
config.pixel_id = ENV["META_PIXEL_ID"]
|
|
19
30
|
config.access_token = ENV["META_ACCESS_TOKEN"]
|
|
20
31
|
config.store_url = "https://minhaloja.com.br"
|
|
32
|
+
config.external_id = ->(order) { order.user_id&.to_s || order.guest_token }
|
|
21
33
|
end
|
|
22
34
|
```
|
|
23
35
|
|
|
36
|
+
`external_id` identifica o cliente entre eventos. O padrão é o id do usuário ou o `guest_token` do pedido. Use um valor que o navegador também conheça, para o pixel enviar o mesmo id. Valores que já são um SHA-256 (64 caracteres hexadecimais) são enviados como estão, sem hash de novo.
|
|
37
|
+
|
|
24
38
|
`store_url` é usado para montar o `event_source_url` de cada evento. Se não for configurado, usa a url da loja do pedido (`order.store.url`). Quando a url vem sem protocolo, a gem adiciona `https://`.
|
|
25
39
|
|
|
26
40
|
## Pixel
|
|
27
41
|
|
|
28
|
-
|
|
42
|
+
O pixel da Meta precisa estar no `<head>` do layout. Ele cria os cookies `_fbp` e `_fbc`, que a gem envia junto de cada evento. Passe os dados do cliente no `init` (correspondência avançada; o pixel faz o hash):
|
|
43
|
+
|
|
44
|
+
```erb
|
|
45
|
+
fbq('init', '<%= j ENV["META_PIXEL_ID"] %>', <%= meta_pixel_user_data(order: current_order, user: current_user).to_json.html_safe %>);
|
|
46
|
+
fbq('track', 'PageView');
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
E, dentro do `<body>`, o tag que dispara no navegador os eventos enviados pelo servidor, com o mesmo `eventID` para a Meta desduplicar:
|
|
50
|
+
|
|
51
|
+
```erb
|
|
52
|
+
<%= meta_pixel_events_tag %>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Ele precisa ficar no `<body>`: com Turbo, scripts do `<head>` só rodam no carregamento completo da página.
|
|
29
56
|
|
|
30
57
|
Obs.: O id do pixel precisa estar dessa forma: `<%= j ENV["NOME_DA_ENV"] %>`. Escrever `ENV["NOME_DA_ENV"]` direto no `<script>` não funciona, porque o navegador não tem acesso ao `ENV`.
|
|
31
58
|
|
|
@@ -39,28 +66,42 @@ class ApplicationController < ActionController::Base
|
|
|
39
66
|
end
|
|
40
67
|
```
|
|
41
68
|
|
|
42
|
-
|
|
69
|
+
O helper também grava o cookie `_fbc` pelo servidor quando a url chega com `fbclid`.
|
|
70
|
+
|
|
71
|
+
Nos pontos do fluxo da loja, chame `track_meta_event`. Ele enfileira o envio pela Conversions API (com cookies `_fbp`/`_fbc` e IP e user agent da visita do Ahoy ou, sem ela, da requisição) e agenda o mesmo evento, com o mesmo `event_id`, para o pixel disparar na próxima página renderizada:
|
|
43
72
|
|
|
44
73
|
```ruby
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
track_meta_event("ViewContent", variant_ids: variants.map(&:id), order_id: current_order&.id)
|
|
75
|
+
track_meta_event("AddToCart", line_item_id: line_item.id, quantity:)
|
|
76
|
+
track_meta_event("InitiateCheckout", order_id: order.id)
|
|
77
|
+
track_meta_event("AddPaymentInfo", payment_id: payment.id)
|
|
78
|
+
track_meta_event("Purchase", order_id: order.id)
|
|
79
|
+
track_meta_event("CompleteRegistration", user:, order_id: current_order&.id)
|
|
80
|
+
track_meta_event("Search", search_string: params[:keywords], content_ids: skus, order_id: current_order&.id)
|
|
49
81
|
```
|
|
50
82
|
|
|
83
|
+
Requisições de prefetch do Turbo (`X-Sec-Purpose: prefetch`) não disparam evento.
|
|
84
|
+
|
|
85
|
+
Na página do pedido, chame `track_meta_purchase_in_browser(order)`. Ele dispara o `Purchase` no navegador para pedidos completados no último dia que ainda não foram disparados nessa sessão, o que cobre pedidos confirmados por job (PIX pago depois).
|
|
86
|
+
|
|
51
87
|
### Eventos suportados
|
|
52
88
|
|
|
53
89
|
| Evento | Quando chamar | Parâmetros |
|
|
54
90
|
|---|---|---|
|
|
91
|
+
| `ViewContent` | Página de produto | `variant_ids:` (variantes que podem ser compradas; a primeira define `value`) e, opcionalmente, `order_id:` |
|
|
55
92
|
| `AddToCart` | Item adicionado ao carrinho | `line_item_id:`, `quantity:` |
|
|
56
93
|
| `InitiateCheckout` | Entrou no checkout | `order_id:` |
|
|
57
94
|
| `AddPaymentInfo` | Pagamento criado com sucesso (PIX gerado, cartão aprovado ou em análise) | `payment_id:` |
|
|
58
95
|
| `Purchase` | Pedido completado (`order.complete` retornou `true`) | `order_id:` |
|
|
96
|
+
| `Search` | Busca feita pelo cliente | `search_string:`, `content_ids:` (SKUs dos primeiros resultados) e, opcionalmente, `order_id:` |
|
|
97
|
+
| `CompleteRegistration` | Conta criada. Conta uma vez por usuário (`event_id` fixo) | `user:` e, opcionalmente, `order_id:` (o carrinho atual, para enviar endereço, telefone e `external_id`) |
|
|
98
|
+
|
|
99
|
+
Fora de um controller (por exemplo, num job que confirma um pagamento PIX), chame `SolidusMeta::EventJob.perform_later` passando `cookies_meta:` e `ahoy_visit_id:` direto, com os valores salvos durante o checkout.
|
|
59
100
|
|
|
60
|
-
|
|
101
|
+
O `value` é o valor dos produtos com descontos, sem frete. No `AddToCart`, é o preço vezes a quantidade adicionada.
|
|
61
102
|
|
|
62
103
|
O `event_time` enviado à Meta é o momento em que o job foi enfileirado.
|
|
63
104
|
|
|
64
105
|
### Adicionando um evento
|
|
65
106
|
|
|
66
|
-
Inclua o nome em `SolidusMeta::Event::EVENTS` e crie um método privado em `SolidusMeta::Event` com o nome em snake_case (`ViewContent` → `view_content`). Ele recebe as opções passadas ao job, define `@order` e retorna `path` e, opcionalmente, `event_id
|
|
107
|
+
Inclua o nome em `SolidusMeta::Event::EVENTS` e crie um método privado em `SolidusMeta::Event` com o nome em snake_case (`ViewContent` → `view_content`). Ele recebe as opções passadas ao job, define `@order` e retorna `path` e, opcionalmente, `event_id` e `custom_data` (que sobrescreve os campos padrão). Sem `event_id`, o id é `<número do pedido>-<event_time>`, então cada disparo conta como um evento separado. Defina um `event_id` fixo quando o evento só pode contar uma vez por pedido, como o `Purchase`.
|
|
@@ -2,14 +2,64 @@ module SolidusMeta
|
|
|
2
2
|
module ControllerHelpers
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
+
BROWSER_EVENTS_KEY = :solidus_meta_browser_events
|
|
6
|
+
TRACKED_PURCHASES_KEY = :solidus_meta_tracked_purchases
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
before_action :store_meta_click_id
|
|
10
|
+
helper SolidusMeta::PixelHelper
|
|
11
|
+
end
|
|
12
|
+
|
|
5
13
|
private
|
|
6
14
|
|
|
15
|
+
def track_meta_event(event_name, **options)
|
|
16
|
+
return if request.headers["X-Sec-Purpose"] == "prefetch"
|
|
17
|
+
|
|
18
|
+
browser_event = SolidusMeta::Event.new.browser_data(event_name, **options)
|
|
19
|
+
event_id = browser_event[:event_id] || SecureRandom.uuid
|
|
20
|
+
|
|
21
|
+
SolidusMeta::EventJob.perform_later(event_name, event_id:, **options, **meta_tracking_data)
|
|
22
|
+
queue_meta_browser_event(event_name, event_id, browser_event[:custom_data])
|
|
23
|
+
mark_meta_purchase_tracked(event_id) if event_name == "Purchase"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def track_meta_purchase_in_browser(order)
|
|
27
|
+
return unless order.complete? && order.completed_at > 1.day.ago
|
|
28
|
+
return if Array(session[TRACKED_PURCHASES_KEY]).include?(order.number)
|
|
29
|
+
|
|
30
|
+
browser_event = SolidusMeta::Event.new.browser_data("Purchase", order_id: order.id)
|
|
31
|
+
queue_meta_browser_event("Purchase", browser_event[:event_id], browser_event[:custom_data])
|
|
32
|
+
mark_meta_purchase_tracked(order.number)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def queue_meta_browser_event(event_name, event_id, custom_data)
|
|
36
|
+
session[BROWSER_EVENTS_KEY] = Array(session[BROWSER_EVENTS_KEY]).last(4) + [{"name" => event_name, "event_id" => event_id, "custom_data" => custom_data.as_json}]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# O cookie de sessão tem limite de 4KB, então só guarda os pedidos mais recentes.
|
|
40
|
+
def mark_meta_purchase_tracked(order_number)
|
|
41
|
+
session[TRACKED_PURCHASES_KEY] = Array(session[TRACKED_PURCHASES_KEY]).last(9) + [order_number]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# O pixel grava o _fbc via JavaScript e o Safari apaga esses cookies em 7 dias; gravado pelo servidor ele dura os 90 dias da janela de atribuição.
|
|
45
|
+
def store_meta_click_id
|
|
46
|
+
fbclid = params[:fbclid].presence
|
|
47
|
+
return if fbclid.nil? || cookies[:_fbc].to_s.end_with?(".#{fbclid}")
|
|
48
|
+
|
|
49
|
+
cookies[:_fbc] = {value: "fb.1.#{(Time.current.to_f * 1000).to_i}.#{fbclid}", expires: 90.days, domain: :all}
|
|
50
|
+
end
|
|
51
|
+
|
|
7
52
|
def cookies_meta
|
|
8
53
|
{fbp: cookies[:_fbp], fbc: cookies[:_fbc]}
|
|
9
54
|
end
|
|
10
55
|
|
|
11
56
|
def meta_tracking_data
|
|
12
|
-
{
|
|
57
|
+
{
|
|
58
|
+
cookies_meta:,
|
|
59
|
+
ahoy_visit_id: (current_visit&.id if respond_to?(:current_visit, true)),
|
|
60
|
+
client_ip_address: request.remote_ip,
|
|
61
|
+
client_user_agent: request.user_agent
|
|
62
|
+
}
|
|
13
63
|
end
|
|
14
64
|
end
|
|
15
65
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module SolidusMeta
|
|
2
|
+
module PixelHelper
|
|
3
|
+
def meta_pixel_user_data(order: nil, user: nil, external_id: nil)
|
|
4
|
+
SolidusMeta::UserData.new(order:, user:, external_id:).to_h
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def meta_pixel_events_tag
|
|
8
|
+
# O Turbo pré-carrega links no hover; sem isso a fila seria consumida por uma página que talvez nunca apareça.
|
|
9
|
+
return if request.headers["X-Sec-Purpose"] == "prefetch"
|
|
10
|
+
|
|
11
|
+
events = session.delete(SolidusMeta::ControllerHelpers::BROWSER_EVENTS_KEY)
|
|
12
|
+
return if events.blank?
|
|
13
|
+
|
|
14
|
+
calls = events.map do |event|
|
|
15
|
+
"fbq('track', #{event["name"].to_json}, #{event["custom_data"].to_json}, {eventID: #{event["event_id"].to_json}});"
|
|
16
|
+
end
|
|
17
|
+
javascript_tag("if (window.fbq) { #{calls.join(" ")} }", nonce: true)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -24,6 +24,10 @@ module SolidusMeta
|
|
|
24
24
|
]
|
|
25
25
|
}
|
|
26
26
|
post("#{@pixel_id}/events", body:, params:)
|
|
27
|
+
rescue Ac::AcError => e
|
|
28
|
+
# Timeout e falha de conexão chegam como AcError genérico com código 0, misturados com erros 4xx que não adianta reenviar.
|
|
29
|
+
raise ConnectionError, e.message if e.response.response.code.zero?
|
|
30
|
+
raise
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SolidusMeta
|
|
2
2
|
class Event
|
|
3
|
-
EVENTS = %w[AddToCart InitiateCheckout AddPaymentInfo Purchase].freeze
|
|
3
|
+
EVENTS = %w[ViewContent AddToCart InitiateCheckout AddPaymentInfo Purchase CompleteRegistration Search].freeze
|
|
4
4
|
|
|
5
5
|
attr_reader :order
|
|
6
6
|
|
|
@@ -8,26 +8,44 @@ module SolidusMeta
|
|
|
8
8
|
@client = client
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def
|
|
11
|
+
def build(event_name, **options)
|
|
12
12
|
raise Error, "evento não suportado: #{event_name}" unless EVENTS.include?(event_name)
|
|
13
13
|
event = send(event_name.underscore, **options)
|
|
14
|
-
event_id = event[:event_id] || "#{order.number}-#{event_time.to_time.to_i}"
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
event_source_url: File.join(SolidusMeta.config.store_url(order.store), event[:path]),
|
|
21
|
-
user_data: user_data(cookies_meta, ahoy_visit_id, fallback: event[:fallback]),
|
|
15
|
+
{
|
|
16
|
+
event_id: event[:event_id],
|
|
17
|
+
event_source_url: File.join(SolidusMeta.config.store_url(order&.store), event[:path]),
|
|
18
|
+
user: event[:user],
|
|
22
19
|
custom_data: {
|
|
23
|
-
|
|
24
|
-
value: order.total.to_f,
|
|
25
|
-
order_id: order.number,
|
|
26
|
-
content_type: "product",
|
|
27
|
-
contents: content_line_items,
|
|
28
|
-
**address_data,
|
|
20
|
+
**(event.fetch(:order_data, true) ? order_custom_data : {}),
|
|
29
21
|
**event.fetch(:custom_data, {})
|
|
30
22
|
}.compact
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def browser_data(event_name, **options)
|
|
27
|
+
event = build(event_name, **options)
|
|
28
|
+
{event_id: event[:event_id], custom_data: event[:custom_data].except(:contents)}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def track(event_name, cookies_meta:, event_id: nil, ahoy_visit_id: nil, client_ip_address: nil, client_user_agent: nil, event_time: Time.current, **options)
|
|
32
|
+
event = build(event_name, **options)
|
|
33
|
+
visit = Ahoy::Visit.find_by(id: ahoy_visit_id) if ahoy_visit_id && defined?(Ahoy::Visit)
|
|
34
|
+
cookies_meta = cookies_meta.to_h.with_indifferent_access
|
|
35
|
+
|
|
36
|
+
@client.send_event(
|
|
37
|
+
event_name,
|
|
38
|
+
event_time:,
|
|
39
|
+
event_id: event_id || event[:event_id] || "#{order.number}-#{event_time.to_time.to_i}",
|
|
40
|
+
event_source_url: event[:event_source_url],
|
|
41
|
+
user_data: {
|
|
42
|
+
**UserData.new(order:, user: event[:user]).hashed,
|
|
43
|
+
client_ip_address: visit&.ip || client_ip_address,
|
|
44
|
+
client_user_agent: visit&.user_agent || client_user_agent,
|
|
45
|
+
fbp: cookies_meta[:fbp],
|
|
46
|
+
fbc: cookies_meta[:fbc]
|
|
47
|
+
}.compact,
|
|
48
|
+
custom_data: event[:custom_data]
|
|
31
49
|
)
|
|
32
50
|
end
|
|
33
51
|
|
|
@@ -40,10 +58,10 @@ module SolidusMeta
|
|
|
40
58
|
{
|
|
41
59
|
event_id: "#{line_item.id}-#{line_item.updated_at.to_i}",
|
|
42
60
|
path: "carrinho",
|
|
43
|
-
fallback: "sem usuário",
|
|
44
61
|
custom_data: {
|
|
45
62
|
currency: line_item.currency,
|
|
46
|
-
value: line_item.
|
|
63
|
+
value: (line_item.price * quantity.to_i).to_f,
|
|
64
|
+
content_ids: [line_item.sku],
|
|
47
65
|
contents: [content_line_item(line_item, quantity)]
|
|
48
66
|
}
|
|
49
67
|
}
|
|
@@ -55,7 +73,7 @@ module SolidusMeta
|
|
|
55
73
|
{
|
|
56
74
|
path: "checkout/address/edit",
|
|
57
75
|
custom_data: {
|
|
58
|
-
num_items: order.line_items.
|
|
76
|
+
num_items: order.line_items.sum(&:quantity)
|
|
59
77
|
}
|
|
60
78
|
}
|
|
61
79
|
end
|
|
@@ -67,9 +85,6 @@ module SolidusMeta
|
|
|
67
85
|
{
|
|
68
86
|
path: "checkout/payment",
|
|
69
87
|
custom_data: {
|
|
70
|
-
currency: payment.currency,
|
|
71
|
-
value: payment.amount.to_f,
|
|
72
|
-
content_id: payment.number,
|
|
73
88
|
payment_type: payment.payment_method.name
|
|
74
89
|
}
|
|
75
90
|
}
|
|
@@ -80,77 +95,86 @@ module SolidusMeta
|
|
|
80
95
|
|
|
81
96
|
{
|
|
82
97
|
event_id: order.number,
|
|
83
|
-
path: "pedidos"
|
|
98
|
+
path: "pedidos/#{order.number}",
|
|
99
|
+
custom_data: {
|
|
100
|
+
num_items: order.line_items.sum(&:quantity)
|
|
101
|
+
}
|
|
84
102
|
}
|
|
85
103
|
end
|
|
86
104
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
visit = Ahoy::Visit.find_by(id: ahoy_visit_id) if ahoy_visit_id && defined?(Ahoy::Visit)
|
|
90
|
-
|
|
91
|
-
{
|
|
92
|
-
em: sha256(email || fallback),
|
|
93
|
-
fn: sha256(name || fallback),
|
|
94
|
-
ph: sha256(phone),
|
|
95
|
-
ct: sha256(address&.city&.downcase),
|
|
96
|
-
st: sha256(address&.state&.name&.downcase),
|
|
97
|
-
zp: sha256(address&.zipcode),
|
|
98
|
-
country: sha256(country),
|
|
99
|
-
external_id: address&.id&.to_s,
|
|
100
|
-
client_user_agent: visit&.user_agent,
|
|
101
|
-
client_ip_address: visit&.ip,
|
|
102
|
-
fbp: cookies_meta[:fbp],
|
|
103
|
-
fbc: cookies_meta[:fbc]
|
|
104
|
-
}.compact
|
|
105
|
-
end
|
|
105
|
+
def complete_registration(user:, order_id: nil)
|
|
106
|
+
@order = Spree::Order.find_by(id: order_id) if order_id
|
|
106
107
|
|
|
107
|
-
def address_data
|
|
108
108
|
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
event_id: "registration-#{user.id}",
|
|
110
|
+
path: "cadastro",
|
|
111
|
+
user:,
|
|
112
|
+
order_data: false,
|
|
113
|
+
custom_data: {
|
|
114
|
+
status: true
|
|
115
|
+
}
|
|
112
116
|
}
|
|
113
117
|
end
|
|
114
118
|
|
|
115
|
-
def
|
|
116
|
-
order
|
|
117
|
-
|
|
119
|
+
def view_content(variant_ids:, order_id: nil)
|
|
120
|
+
@order = Spree::Order.find_by(id: order_id) if order_id
|
|
121
|
+
variants = Spree::Variant.where(id: variant_ids).includes(:product).index_by(&:id).values_at(*variant_ids).compact
|
|
122
|
+
main_variant = variants.first
|
|
118
123
|
|
|
119
|
-
def content_line_item(line_item, quantity)
|
|
120
124
|
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
path: "produtos/#{main_variant.product.slug}",
|
|
126
|
+
order_data: false,
|
|
127
|
+
custom_data: {
|
|
128
|
+
currency: main_variant.default_price&.currency || Spree::Config.currency,
|
|
129
|
+
value: main_variant.price.to_f,
|
|
130
|
+
content_type: "product",
|
|
131
|
+
content_ids: variants.map(&:sku),
|
|
132
|
+
contents: variants.map { |variant| {id: variant.sku, quantity: 1, item_price: variant.price.to_f} }
|
|
133
|
+
}
|
|
124
134
|
}
|
|
125
135
|
end
|
|
126
136
|
|
|
127
|
-
def
|
|
128
|
-
order.
|
|
129
|
-
end
|
|
137
|
+
def search(search_string:, content_ids: [], order_id: nil)
|
|
138
|
+
@order = Spree::Order.find_by(id: order_id) if order_id
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
{
|
|
141
|
+
path: "busca?#{{keywords: search_string}.to_query}",
|
|
142
|
+
order_data: false,
|
|
143
|
+
custom_data: {
|
|
144
|
+
search_string:,
|
|
145
|
+
content_type: "product",
|
|
146
|
+
content_ids:
|
|
147
|
+
}
|
|
148
|
+
}
|
|
133
149
|
end
|
|
134
150
|
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
end
|
|
151
|
+
def order_custom_data
|
|
152
|
+
return {} unless order
|
|
138
153
|
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
{
|
|
155
|
+
currency: order.currency,
|
|
156
|
+
value: products_value,
|
|
157
|
+
order_id: order.number,
|
|
158
|
+
content_type: "product",
|
|
159
|
+
content_ids: order.line_items.map(&:sku),
|
|
160
|
+
contents: content_line_items
|
|
161
|
+
}
|
|
141
162
|
end
|
|
142
163
|
|
|
143
|
-
def
|
|
144
|
-
(order.
|
|
164
|
+
def products_value
|
|
165
|
+
(order.total - order.shipment_total - order.shipments.sum(&:promo_total)).to_f
|
|
145
166
|
end
|
|
146
167
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
"55" + address.phone.remove(/\D/).remove(/^55/)
|
|
168
|
+
def content_line_items
|
|
169
|
+
order.line_items.map { |line_item| content_line_item(line_item, line_item.quantity) }
|
|
150
170
|
end
|
|
151
171
|
|
|
152
|
-
def
|
|
153
|
-
|
|
172
|
+
def content_line_item(line_item, quantity)
|
|
173
|
+
{
|
|
174
|
+
id: line_item.sku,
|
|
175
|
+
quantity: quantity.to_i,
|
|
176
|
+
item_price: line_item.price.to_f
|
|
177
|
+
}
|
|
154
178
|
end
|
|
155
179
|
end
|
|
156
180
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module SolidusMeta
|
|
2
|
+
class UserData
|
|
3
|
+
def initialize(order: nil, user: nil, external_id: nil)
|
|
4
|
+
@order = order
|
|
5
|
+
@user = user || order&.user
|
|
6
|
+
@external_id = external_id
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_h
|
|
10
|
+
{
|
|
11
|
+
em: email,
|
|
12
|
+
ph: phone,
|
|
13
|
+
fn: first_name,
|
|
14
|
+
ln: last_name,
|
|
15
|
+
ct: city,
|
|
16
|
+
st: state,
|
|
17
|
+
zp: zipcode,
|
|
18
|
+
country:,
|
|
19
|
+
external_id:
|
|
20
|
+
}.compact_blank
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def hashed
|
|
24
|
+
to_h.transform_values { |value| value.match?(/\A\h{64}\z/) ? value : Digest::SHA256.hexdigest(value) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :order, :user
|
|
30
|
+
|
|
31
|
+
def address
|
|
32
|
+
order&.ship_address || order&.bill_address || user.try(:ship_address) || user.try(:bill_address)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def email
|
|
36
|
+
(order&.email.presence || user.try(:email).presence || user.try(:email_address))&.strip&.downcase
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def phone
|
|
40
|
+
digits = address&.phone.to_s.gsub(/\D/, "").sub(/\A0+/, "")
|
|
41
|
+
return if digits.blank?
|
|
42
|
+
|
|
43
|
+
digits = "55#{digits}" if digits.length.in?(10..11)
|
|
44
|
+
digits if digits.length.in?(12..13)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def name_parts
|
|
48
|
+
@name_parts ||= (order&.name.presence || address&.name.presence || user.try(:name)).to_s.downcase.split
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def first_name
|
|
52
|
+
name_parts.first
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def last_name
|
|
56
|
+
name_parts.last if name_parts.size > 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def city
|
|
60
|
+
I18n.transliterate(address.city.to_s).downcase.gsub(/[^a-z]/, "") if address
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def state
|
|
64
|
+
(address.state&.abbr || address.state_name).to_s.downcase if address
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def zipcode
|
|
68
|
+
address&.zipcode.to_s.gsub(/\D/, "")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def country
|
|
72
|
+
(address&.country || Spree::Country.default)&.iso&.downcase
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def external_id
|
|
76
|
+
@external_id.presence || (SolidusMeta.config.external_id(order) if order)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidusMeta
|
|
4
4
|
class Configuration
|
|
5
|
-
attr_writer :pixel_id, :access_token, :store_url
|
|
5
|
+
attr_writer :pixel_id, :access_token, :store_url, :external_id
|
|
6
6
|
|
|
7
7
|
def pixel_id
|
|
8
8
|
@pixel_id || ENV["META_PIXEL_ID"]
|
|
@@ -18,5 +18,9 @@ module SolidusMeta
|
|
|
18
18
|
raise Error, "store_url não configurado: defina SolidusMeta.config.store_url, META_STORE_URL ou a url da loja" unless url
|
|
19
19
|
url.start_with?("http") ? url : "https://#{url}"
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
def external_id(order)
|
|
23
|
+
(@external_id || ->(o) { o.user_id&.to_s || o.guest_token }).call(order)&.to_s
|
|
24
|
+
end
|
|
21
25
|
end
|
|
22
26
|
end
|
data/lib/solidus_meta/version.rb
CHANGED
data/lib/solidus_meta.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solidus_meta
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- marialuiza1324
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ac
|
|
@@ -61,10 +61,12 @@ extra_rdoc_files: []
|
|
|
61
61
|
files:
|
|
62
62
|
- README.md
|
|
63
63
|
- app/controllers/concerns/solidus_meta/controller_helpers.rb
|
|
64
|
+
- app/helpers/solidus_meta/pixel_helper.rb
|
|
64
65
|
- app/jobs/solidus_meta/base_job.rb
|
|
65
66
|
- app/jobs/solidus_meta/event_job.rb
|
|
66
67
|
- app/models/solidus_meta/client.rb
|
|
67
68
|
- app/models/solidus_meta/event.rb
|
|
69
|
+
- app/models/solidus_meta/user_data.rb
|
|
68
70
|
- lib/solidus_meta.rb
|
|
69
71
|
- lib/solidus_meta/configuration.rb
|
|
70
72
|
- lib/solidus_meta/engine.rb
|