├── README.md
├── composer.json
├── config
└── klaviyo.php
├── resources
└── views
│ ├── components
│ ├── default.blade.php
│ └── identify.blade.php
│ ├── initialize.blade.php
│ ├── object.blade.php
│ └── recursive.blade.php
└── src
├── Contracts
├── KlaviyoIdentity.php
└── ViewedProduct.php
├── Jobs
├── SendKlaviyoIdentify.php
└── SendKlaviyoTrack.php
├── Klaviyo.php
├── KlaviyoClient.php
├── LaravelKlaviyoServiceProvider.php
├── TrackEvent.php
├── UserEventSubscriber.php
├── View
└── Creators
│ └── InitializeCreator.php
└── helpers.php
/README.md:
--------------------------------------------------------------------------------
1 | # laravel-klaviyo
2 |
3 | This package assists with interacting with [Klaviyo](https://www.klaviyo.com/) to track client and server-side metrics and the REST api.
4 |
5 | ## Requirements
6 |
7 | For server-side track, identify or REST api calls this package utilises the Laravel HTTP Client.
8 |
9 | It is recommended that server-side events are processed in the background, by default jobs are placed on the `klaviyo` queue.
10 |
11 | ## Installation
12 |
13 | You can install the package via composer:
14 |
15 | ```bash
16 | composer require eonvisualmedia/laravel-klaviyo
17 | ```
18 |
19 | The package will automatically register itself.
20 |
21 | You can optionally publish the config file with:
22 |
23 | ```bash
24 | php artisan vendor:publish --provider="EonVisualMedia\LaravelKlaviyo\LaravelKlaviyoServiceProvider" --tag="tags-config"
25 | ```
26 |
27 | Depending upon your intended usage minimally you'll need to configure your environment with your public and private api keys.
28 |
29 | ```
30 | // .env
31 |
32 | KLAVIYO_PRIVATE_API_KEY=
33 | KLAVIYO_PUBLIC_API_KEY=
34 | ```
35 |
36 | ## Usage
37 |
38 | ### Basic Example
39 |
40 | First you'll need to include the Klaviyo JavaScript API for Identification and Tracking by including it at the end of your layout just before the closing body tag.
41 |
42 | ```
43 | // layout.blade.php
44 |
45 |
46 |
47 | {{-- ... --}}
48 | @include('klaviyo::initialize')
49 |
50 |
51 | ```
52 |
53 | #### To add identity
54 |
55 | If the current user is not identified and `Auth::user()` is an instance of `EonVisualMedia\LaravelKlaviyo\Contracts\KlaviyoIdentity` then the `getKlaviyoIdentity` method will be called and an identify
56 | event added to the page.
57 |
58 | Alternatively the identify method may be called explicitly, for instance after user login.
59 |
60 | ```php
61 | Klaviyo::identify([
62 | 'email' => 'foo@example.com',
63 | 'first_name' => 'Foo',
64 | 'last_name' => 'Bar'
65 | ]);
66 | ```
67 |
68 | #### Track events client-side
69 |
70 | ```php
71 | Klaviyo::push('track', 'Added to Cart', [
72 | '$value' => 100,
73 | 'AddedTitle' => 'Widget A'
74 | ]);
75 | ```
76 |
77 | #### Track events server-side:
78 |
79 | To queue server-side events.
80 |
81 | ```php
82 | Klaviyo::track(TrackEvent::make(
83 | 'Placed Order',
84 | [
85 | 'unique_id' => '1234_WINNIEPOOH',
86 | 'value' => 9.99,
87 | ]
88 | ));
89 | ```
90 |
91 | You can optionally also specify the customer properties and timestamp, if not specified the customer will attempt to be identified by their cookie ($exchange_id) or their user model if `Auth::user()`
92 | is an instance of `EonVisualMedia\LaravelKlaviyo\Contracts\KlaviyoIdentity`.
93 |
94 | ```php
95 | Klaviyo::track(TrackEvent::make(
96 | 'Placed Order',
97 | [
98 | 'unique_id' => '1234_WINNIEPOOH',
99 | 'value' => 9.99,
100 | ],
101 | [
102 | 'email' => 'foo@example.com',
103 | 'first_name' => 'Foo',
104 | 'last_name' => 'Bar',
105 | ],
106 | now()->addWeeks(-1)
107 | ));
108 | ```
109 |
110 | ### Monitoring Login Events
111 |
112 | By default, the package will subscribe to `Illuminate\Auth\Events\Login` events and dispatch an `klaviyo.identify(...)` call.
113 |
114 | This behaviour can be disabled using the config option `identify_on_login`.
115 |
116 | ### Advanced usage
117 |
118 | #### Macros
119 |
120 | The package allows you to extend its functionality, this can be helpful for creating reusable events.
121 |
122 | You may define macros within the `boot` method of a service provider, either your own or within the application's `App\Providers\AppServiceProvider` class.
123 |
124 | ```php
125 | use EonVisualMedia\LaravelKlaviyo\Klaviyo;
126 |
127 | /**
128 | * Bootstrap any application services.
129 | *
130 | * @return void
131 | */
132 | public function boot()
133 | {
134 | Klaviyo::macro(
135 | 'fulfilled_order',
136 | function (Transaction $transaction) {
137 | Klaviyo::track(TrackEvent::make(
138 | 'Fulfilled Order',
139 | $transaction->toKlaviyo(),
140 | $transaction->user,
141 | $transaction->created_at
142 | ));
143 | }
144 | );
145 | }
146 | ```
147 |
148 | With the macro defined you may invoke it anywhere in your application:
149 |
150 | ```php
151 | Klaviyo::fulfilled_order($transaction);
152 | ```
153 |
154 | #### REST
155 |
156 | You may interact with the Klaviyo REST api using the Laravel HTTP Client, calls forwarded via KlaviyoClient append a `Authorization: Klaviyo-API-Key your-private-api-key` header to requests.
157 |
158 | ```php
159 | Klaviyo::get('lists');
160 |
161 | Klaviyo::post("profile-subscription-bulk-create-jobs", [
162 | 'data' => [
163 | 'type' => 'profile-subscription-bulk-create-job',
164 | 'attributes' => [
165 | 'profiles' => [
166 | 'data' => [
167 | [
168 | 'type' => 'profile',
169 | 'attributes' => [
170 | 'email' => 'foo@example.com',
171 | 'subscriptions' => [
172 | 'email' => [
173 | 'marketing' => [
174 | 'consent' => 'SUBSCRIBED'
175 | ]
176 | ],
177 | ]
178 | ]
179 | ]
180 | ]
181 | ]
182 | ],
183 | 'relationships' => [
184 | 'list' => [
185 | 'data' => [
186 | 'type' => 'list',
187 | 'id' => $list_id
188 | ]
189 | ]
190 | ]
191 | ]
192 | ]);
193 |
194 | Klaviyo::delete("profile-subscription-bulk-delete-jobs", [
195 | 'data' => [
196 | 'type' => 'profile-subscription-bulk-delete-job',
197 | 'attributes' => [
198 | 'profiles' => [
199 | 'data' => [
200 | [
201 | 'type' => 'profile',
202 | 'attributes' => [
203 | 'email' => 'foo@example.com',
204 | ]
205 | ]
206 | ]
207 | ]
208 | ],
209 | 'relationships' => [
210 | 'list' => [
211 | 'data' => [
212 | 'type' => 'list',
213 | 'id' => $list_id
214 | ]
215 | ]
216 | ]
217 | ]
218 | ]);
219 | ```
220 |
221 | ## Upgrading from v1
222 |
223 | The [Klaviyo](https://www.klaviyo.com/) legacy v1/v2 APIs are scheduled to retire June 30th, 2024.
224 |
225 | I would encourage you to review especially the breaking changes on the [Klaviyo: API versioning and deprecation policy](https://developers.klaviyo.com/en/docs/api_versioning_and_deprecation_policy)
226 |
227 | The API changes have therefore necessitated a few breaking changes to this package, specifically the payloads required for `identify` and `push`.
228 | See the examples below, for example the `getKlaviyoIdentity` response replaces `$email` with `email`.
229 |
230 | Also take note that client and server payloads for identify/profile have a few differences `klaviyo_client_to_server_profile` may be of assistance help converting client payloads to server profiles.
231 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eonvisualmedia/laravel-klaviyo",
3 | "description": "Interact with the Klaviyo API from your Laravel app",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Michael Nightingale",
8 | "email": "mnightingale@eon-media.com"
9 | }
10 | ],
11 | "require": {
12 | "php": "^8.0",
13 | "guzzlehttp/guzzle": "^7.0",
14 | "illuminate/bus": "^8.0|^9.0|^10.0|^11.0",
15 | "illuminate/http": "^8.0|^9.0|^10.0|^11.0",
16 | "illuminate/support": "^8.0|^9.0|^10.0|^11.0"
17 | },
18 | "require-dev": {
19 | "orchestra/testbench": "^7.0",
20 | "phpunit/phpunit": "^9.3"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "EonVisualMedia\\LaravelKlaviyo\\": "src"
25 | },
26 | "files": [
27 | "src/helpers.php"
28 | ]
29 | },
30 | "autoload-dev": {
31 | "psr-4": {
32 | "EonVisualMedia\\LaravelKlaviyo\\Test\\": "tests"
33 | }
34 | },
35 | "config": {
36 | "sort-packages": true
37 | },
38 | "extra": {
39 | "laravel": {
40 | "providers": [
41 | "EonVisualMedia\\LaravelKlaviyo\\LaravelKlaviyoServiceProvider"
42 | ],
43 | "aliases": {
44 | "Klaviyo": "EonVisualMedia\\LaravelKlaviyo\\Klaviyo"
45 | }
46 | }
47 | },
48 | "minimum-stability": "dev",
49 | "prefer-stable": true
50 | }
51 |
--------------------------------------------------------------------------------
/config/klaviyo.php:
--------------------------------------------------------------------------------
1 | (bool)env('KLAVIYO_ENABLED', true),
10 |
11 | 'endpoint' => env('KLAVIYO_ENDPOINT', 'https://a.klaviyo.com/api/'),
12 |
13 | 'api_version' => env('KLAVIYO_API_VERSION', '2024-05-15'),
14 |
15 | /**
16 | * The queue on which jobs will be processed.
17 | */
18 | 'queue' => env('KLAVIYO_QUEUE', 'klaviyo'),
19 |
20 | 'private_api_key' => env('KLAVIYO_PRIVATE_API_KEY', ''),
21 |
22 | 'public_api_key' => env('KLAVIYO_PUBLIC_API_KEY', ''),
23 |
24 | 'identity_key_name' => env('KLAVIYO_IDENTITY_KEY_NAME', 'email'),
25 |
26 | /**
27 | * Key under which to flash data to the session.
28 | */
29 | 'session_key' => env('KLAVIYO_SESSION_KEY', '_klaviyo'),
30 |
31 | /**
32 | * Push a klaviyo.identify(...) call after a \Illuminate\Auth\Events\Login event
33 | */
34 | 'identify_on_login' => env('KLAVIYO_IDENTITY_ON_LOGIN', true),
35 |
36 | ];
37 |
--------------------------------------------------------------------------------
/resources/views/components/default.blade.php:
--------------------------------------------------------------------------------
1 | @switch(count($item))
2 | @case(1)
3 | klaviyo.push([@json($item[0])]);
4 | @break
5 | @case(2)
6 | klaviyo.push([@json($item[0]), @json($item[1])]);
7 | @break
8 | @case(3)
9 | klaviyo.push([@json($item[0]), @json($item[1]), @json($item[2])]);
10 | @break
11 | @endswitch
12 |
--------------------------------------------------------------------------------
/resources/views/components/identify.blade.php:
--------------------------------------------------------------------------------
1 | @if($slot->isEmpty())
2 | klaviyo.identify(@json($item[1]));
3 | @else
4 | klaviyo.identify(@json($item[1]), function () {
5 | {{ $slot }}
6 | });
7 | @endif
8 |
--------------------------------------------------------------------------------
/resources/views/initialize.blade.php:
--------------------------------------------------------------------------------
1 | @if($enabled)
2 |
3 | @include('klaviyo::object')
4 | @if($data->isNotEmpty())
5 |
8 | @endif
9 | @endif
10 |
--------------------------------------------------------------------------------
/resources/views/object.blade.php:
--------------------------------------------------------------------------------
1 | {{-- https://developers.klaviyo.com/en/docs/introduction_to_the_klaviyo_object --}}
2 |
5 |
--------------------------------------------------------------------------------
/resources/views/recursive.blade.php:
--------------------------------------------------------------------------------
1 | @foreach($items as $item)
2 | @if($item[0] === 'identify')
3 |
4 | @include('klaviyo::recursive', ['items' => $items->slice($loop->index + 1)])
5 |
6 | @break
7 | @endif
8 |
9 | @endforeach
10 |
--------------------------------------------------------------------------------
/src/Contracts/KlaviyoIdentity.php:
--------------------------------------------------------------------------------
1 | onQueue(config('klaviyo.queue'));
19 | }
20 |
21 | public function handle(KlaviyoClient $client)
22 | {
23 | $client
24 | ->post('profile-import', [
25 | 'data' => [
26 | 'type' => 'profile',
27 | 'attributes' => klaviyo_client_to_server_profile($this->attributes)
28 | ]
29 | ])
30 | ->throw(function ($response, $exception) {
31 | Log::error("Klaviyo identify request failed", [
32 | 'response' => $response->json(),
33 | ]);
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Jobs/SendKlaviyoTrack.php:
--------------------------------------------------------------------------------
1 | onQueue(config('klaviyo.queue'));
34 | $this->events = $events;
35 | }
36 |
37 | public function handle(KlaviyoClient $client)
38 | {
39 | $requests = function (Pool $pool) use ($client) {
40 | foreach ($this->events as $event) {
41 | yield $pool
42 | ->acceptJson()
43 | ->asJson()
44 | ->withToken($client->getPrivateKey(), 'Klaviyo-API-Key')
45 | ->withHeaders([
46 | 'revision' => $client->getApiVersion()
47 | ])
48 | ->post($client->getEndpoint().'events', [
49 | 'data' => $event->toPayload()
50 | ]);
51 | }
52 | };
53 |
54 | $client->pool(function (Pool $pool) use ($requests) {
55 | Each::ofLimit(
56 | $requests($pool),
57 | $this->concurrency,
58 | fn(\Illuminate\Http\Client\Response $response, $index) => $response->throw(function (\Illuminate\Http\Client\Response $response, $exception) {
59 | Log::error("Klaviyo track request failed", [
60 | 'response' => $response->json(),
61 | ]);
62 | }),
63 | )->wait();
64 | });
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Klaviyo.php:
--------------------------------------------------------------------------------
1 | endpoint = $config['endpoint'] ?? '';
110 | $this->privateKey = $config['private_api_key'] ?? '';
111 | $this->publicKey = $config['public_api_key'] ?? '';
112 | $this->apiVersion = $config['api_version'] ?: throw new InvalidArgumentException('Invalid API Version');
113 | $this->identityKeyName = $config['identity_key_name'] ?: throw new InvalidArgumentException('Invalid default identity key name');
114 | $this->enabled = $config['enabled'] ?? true;
115 |
116 | $this->pushCollection = new Collection();
117 | }
118 |
119 | /**
120 | * @return string
121 | */
122 | public function getEndpoint(): string
123 | {
124 | return $this->endpoint;
125 | }
126 |
127 | /**
128 | * @return string
129 | */
130 | public function getPrivateKey(): string
131 | {
132 | return $this->privateKey;
133 | }
134 |
135 | /**
136 | * @return string
137 | */
138 | public function getPublicKey(): string
139 | {
140 | return $this->publicKey;
141 | }
142 |
143 | /**
144 | * @return string
145 | */
146 | public function getApiVersion(): string
147 | {
148 | return $this->apiVersion;
149 | }
150 |
151 | /**
152 | * The key for the identity.
153 | *
154 | * @return string
155 | */
156 | public function getIdentityKeyName(): string
157 | {
158 | return $this->identityKeyName;
159 | }
160 |
161 | /**
162 | * Check whether Klaviyo script rendering and server-side jobs is enabled.
163 | *
164 | * @return bool
165 | */
166 | public function isEnabled(): bool
167 | {
168 | return $this->enabled;
169 | }
170 |
171 | /**
172 | * Enable Klaviyo script rendering and server-side jobs.
173 | */
174 | public function enable()
175 | {
176 | $this->enabled = true;
177 | }
178 |
179 | /**
180 | * Disable Klaviyo script rendering and server-side jobs.
181 | */
182 | public function disable()
183 | {
184 | $this->enabled = false;
185 | }
186 |
187 | /**
188 | * Submit a server-side track event to Klaviyo.
189 | *
190 | * @param TrackEvent ...$events
191 | * @return void
192 | */
193 | public function track(TrackEvent ...$events)
194 | {
195 | if (! $this->isEnabled()) {
196 | return;
197 | }
198 |
199 | $events = collect($events)
200 | ->reject(fn($event) => empty($event->identity));
201 |
202 | if ($events->isNotEmpty()) {
203 | dispatch(new SendKlaviyoTrack(...$events->all()));
204 | }
205 | }
206 |
207 | /**
208 | * Submit a server-side identify event to Klaviyo.
209 | *
210 | * @param KlaviyoIdentity|string|array|null $identity
211 | * @return void
212 | *
213 | * @throws InvalidArgumentException
214 | */
215 | public function identify(KlaviyoIdentity|string|array $identity = null)
216 | {
217 | if (! $this->isEnabled()) {
218 | return;
219 | }
220 |
221 | $identity = $this->resolveIdentity($identity ?? Auth::user());
222 | dispatch(new SendKlaviyoIdentify($identity));
223 | }
224 |
225 | /**
226 | * Resolve identity or profile of user
227 | *
228 | * @throws InvalidArgumentException
229 | */
230 | public function resolveIdentity(KlaviyoIdentity|string|array|null $identity = null): ?array
231 | {
232 | if ($identity === null && $this->isIdentified()) {
233 | return $this->getIdentity();
234 | }
235 |
236 | $identity = with($identity ?? Auth::user(), function ($value) {
237 | if ($value instanceof KlaviyoIdentity) {
238 | return $value->getKlaviyoIdentity();
239 | } elseif (is_string($value)) {
240 | return [$this->getIdentityKeyName() => $value];
241 | } elseif (is_array($value)) {
242 | return $value;
243 | } else {
244 | return null;
245 | }
246 | });
247 |
248 | if (empty(array_intersect_key($identity ?? [], array_flip($this->identifyAttributes)))) {
249 | throw new InvalidArgumentException(
250 | sprintf(
251 | 'Identify requires one of the following fields: %s',
252 | implode(', ', $this->identifyAttributes)
253 | )
254 | );
255 | }
256 |
257 | return $identity;
258 | }
259 |
260 | /**
261 | * Decode the __kla_id cookie.
262 | *
263 | * @return array
264 | */
265 | public function getDecodedCookie(): array
266 | {
267 | return json_decode(base64_decode(Cookie::get('__kla_id', '')), true) ?? [];
268 | }
269 |
270 | /**
271 | * Does the \Illuminate\Http\Request cookie contain an $exchange_id?
272 | *
273 | * @return bool
274 | */
275 | public function isIdentified(): bool
276 | {
277 | return $this->getIdentity() !== null;
278 | }
279 |
280 | /**
281 | * Retrieve the $exchange_id from cookie.
282 | *
283 | * @return array|null
284 | */
285 | public function getIdentity(): ?array
286 | {
287 | if (! empty($value = Arr::get($this->getDecodedCookie(), '$exchange_id'))) {
288 | return ['_kx' => $value];
289 | } elseif (! empty($value = Arr::get($this->getDecodedCookie(), '$email'))) {
290 | return ['email' => $value];
291 | } else {
292 | return null;
293 | }
294 | }
295 |
296 | /**
297 | * @return Collection
298 | */
299 | public function getPushCollection(): Collection
300 | {
301 | return $this->pushCollection;
302 | }
303 |
304 | /**
305 | * Push an event to be rendered by the client to the beginning of the collection.
306 | *
307 | * @throws InvalidArgumentException
308 | */
309 | public function prepend(...$values)
310 | {
311 | if (count($values) === 0) {
312 | throw new InvalidArgumentException('Not enough arguments for prepend.');
313 | } elseif (count($values) > 3) {
314 | throw new InvalidArgumentException('Too many arguments for prepend.');
315 | }
316 |
317 | $this->pushCollection->prepend($values);
318 | }
319 |
320 | /**
321 | * Push an event to be rendered by the client.
322 | *
323 | * @throws InvalidArgumentException
324 | */
325 | public function push(...$values)
326 | {
327 | if (count($values) === 0) {
328 | throw new InvalidArgumentException('Not enough arguments for push.');
329 | } elseif (count($values) > 3) {
330 | throw new InvalidArgumentException('Too many arguments for push.');
331 | }
332 |
333 | $this->pushCollection->push($values);
334 | }
335 |
336 | /**
337 | * Push a viewed product event to be rendered by the client.
338 | *
339 | * @param ViewedProduct $product
340 | * @return void
341 | *
342 | * @throws InvalidArgumentException
343 | */
344 | public function pushViewed(ViewedProduct $product)
345 | {
346 | $item = $product->getViewedProductProperties();
347 | $this->push('track', 'Viewed Product', $item);
348 | $this->push('trackViewedItem', $item);
349 | }
350 |
351 | protected function client(): \Illuminate\Http\Client\PendingRequest
352 | {
353 | return Http::baseUrl($this->getEndpoint())
354 | ->acceptJson()
355 | ->asJson()
356 | ->withToken($this->privateKey, 'Klaviyo-API-Key')
357 | ->withHeaders([
358 | 'revision' => $this->getApiVersion()
359 | ]);
360 | }
361 |
362 | public function __call($method, $parameters)
363 | {
364 | if (static::hasMacro($method)) {
365 | return $this->macroCall($method, $parameters);
366 | }
367 |
368 | return $this->forwardCallTo($this->client(), $method, $parameters);
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/src/LaravelKlaviyoServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
21 | $this->publishes([
22 | __DIR__.'/../config/klaviyo.php' => config_path('klaviyo.php'),
23 | ], ['klaviyo', 'klaviyo-config']);
24 | }
25 |
26 | View::creator('klaviyo::initialize', InitializeCreator::class);
27 |
28 | Event::subscribe(UserEventSubscriber::class);
29 | }
30 |
31 | /**
32 | * Register any application services.
33 | *
34 | * @return void
35 | */
36 | public function register()
37 | {
38 | $this->mergeConfigFrom(
39 | __DIR__.'/../config/klaviyo.php', 'klaviyo'
40 | );
41 |
42 | $this->loadViewsFrom(
43 | __DIR__.'/../resources/views', 'klaviyo'
44 | );
45 |
46 | $this->app->singleton(KlaviyoClient::class, function () {
47 | return new KlaviyoClient(
48 | $this->app['config']->get('klaviyo', [])
49 | );
50 | });
51 |
52 | $this->app->alias(KlaviyoClient::class, 'klaviyo');
53 |
54 | $this->app->resolving(EncryptCookies::class, function (EncryptCookies $middleware) {
55 | $middleware->disableFor('__kla_id');
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/TrackEvent.php:
--------------------------------------------------------------------------------
1 | id = Str::uuid()->toString();
23 | $this->identity = Klaviyo::resolveIdentity($identity);
24 | $this->timestamp = $time ?? Carbon::now();
25 | }
26 |
27 | public static function make(
28 | string $metric_name,
29 | array $payload = [],
30 | KlaviyoIdentity|string|array $identity = null,
31 | Carbon $timestamp = null
32 | ): TrackEvent
33 | {
34 | return new static($metric_name, $payload, $identity, $timestamp);
35 | }
36 |
37 | /**
38 | * @return string
39 | */
40 | public function getEvent(): string
41 | {
42 | return $this->metric_name;
43 | }
44 |
45 | /**
46 | * @param string $event
47 | * @return TrackEvent
48 | */
49 | public function setEvent(string $event): TrackEvent
50 | {
51 | $this->metric_name = $event;
52 |
53 | return $this;
54 | }
55 |
56 | /**
57 | * @return array|null
58 | */
59 | public function getProperties(): array|null
60 | {
61 | return $this->payload;
62 | }
63 |
64 | /**
65 | * @param array|null $properties
66 | * @return TrackEvent
67 | */
68 | public function setProperties(array|null $properties): TrackEvent
69 | {
70 | $this->payload = $properties;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * @return KlaviyoIdentity|string|array|null
77 | */
78 | public function getIdentity(): KlaviyoIdentity|string|array|null
79 | {
80 | return $this->identity;
81 | }
82 |
83 | /**
84 | * @param KlaviyoIdentity|string|array|null $identity
85 | * @return TrackEvent
86 | */
87 | public function setIdentity(KlaviyoIdentity|string|array|null $identity): TrackEvent
88 | {
89 | $this->identity = Klaviyo::resolveIdentity($identity);
90 |
91 | return $this;
92 | }
93 |
94 | /**
95 | * @return Carbon|null
96 | */
97 | public function getTimestamp(): Carbon|null
98 | {
99 | return $this->timestamp;
100 | }
101 |
102 | /**
103 | * @param Carbon|null $timestamp
104 | * @return TrackEvent
105 | * @deprecated $timestamp
106 | */
107 | public function setTimestamp(Carbon $timestamp = null): TrackEvent
108 | {
109 | $this->timestamp = $timestamp;
110 |
111 | return $this;
112 | }
113 |
114 | public function toPayload(): array
115 | {
116 | return [
117 | 'type' => 'event',
118 | 'attributes' => array_replace_recursive([
119 | 'properties' => [],
120 | 'time' => $this->timestamp->toIso8601String(),
121 | 'unique_id' => $this->id,
122 | 'metric' => [
123 | 'data' => [
124 | 'type' => 'metric',
125 | 'attributes' => [
126 | 'name' => $this->metric_name,
127 | ]
128 | ]
129 | ],
130 | 'profile' => [
131 | 'data' => [
132 | 'type' => 'profile',
133 | 'attributes' => klaviyo_client_to_server_profile($this->identity)
134 | ],
135 | ],
136 | ], $this->payload),
137 | ];
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/UserEventSubscriber.php:
--------------------------------------------------------------------------------
1 | user instanceof KlaviyoIdentity) {
21 | session()->flash(config('klaviyo.session_key').'.identify');
22 | }
23 | }
24 |
25 | /**
26 | * Register the listeners for the subscriber.
27 | */
28 | public function subscribe(Dispatcher $events): void
29 | {
30 | $events->listen(
31 | Login::class,
32 | [UserEventSubscriber::class, 'handleUserLogin']
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/View/Creators/InitializeCreator.php:
--------------------------------------------------------------------------------
1 | sessionKey = $config->get('klaviyo.session_key');
18 | }
19 |
20 | public function create(View $view)
21 | {
22 | if ($this->shouldIdentify()) {
23 | $user = Auth::user();
24 | if ($user instanceof KlaviyoIdentity) {
25 | $this->client->prepend('identify', $user->getKlaviyoIdentity());
26 | }
27 | }
28 |
29 | $view
30 | ->with('enabled', $this->client->isEnabled())
31 | ->with('publicKey', $this->client->getPublicKey())
32 | ->with('data', $this->client->getPushCollection());
33 | }
34 |
35 | protected function shouldIdentify(): bool
36 | {
37 | if (session()->has($this->sessionKey.'.identify')) {
38 | return true;
39 | }
40 |
41 | // If the identity isn't already set in cookie get the identity from the current user unless an identity is already pending push
42 | if (! $this->client->isIdentified() && $this->client->getPushCollection()->filter(fn($item) => $item[0] === 'identify')->isEmpty()) {
43 | return true;
44 | }
45 |
46 | return false;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | keyBy(fn($item, $key) => in_array(explode('.', $key)[0], KlaviyoClient::SERVER_PROFILE_ATTRIBUTES) ? $key : "properties.{$key}")
17 | ->undot()
18 | ->all();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------