Skip to content
Merged

1.1 #54

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e27aad9
Add missing event name (shop_customer_default_address_update)
tomkalon Sep 26, 2025
8091cd4
Merge pull request #27 from BitBagCommerce/bugfix/add-missing-event-name
tomkalon Sep 26, 2025
5028d04
[UC-25] Update CustomerPayloadBuilder
tomkalon Sep 29, 2025
e73db34
Merge pull request #28 from BitBagCommerce/bugfix/customer-payload
tomkalon Sep 29, 2025
d65320a
[UC-25] Encode query parameter value in UserApi
tomkalon Sep 30, 2025
a6ac1e6
[UC-23] Remove product feed details from documentation
tomkalon Sep 30, 2025
8d0c50d
Merge pull request #30 from BitBagCommerce/bugfix/user-search
tomkalon Sep 30, 2025
778e108
[UC-26] Refactor user_com_customer_info script to improve null and em…
tomkalon Sep 30, 2025
1d829a0
[UC-28] Update product event mappings in OrderUpdateManagerInterface
tomkalon Oct 1, 2025
4617766
[UC-29] Add event to CustomerWithKeyUpdater
tomkalon Oct 1, 2025
0954583
Merge pull request #34 from BitBagCommerce/UC-28-product-event-name
tomkalon Oct 1, 2025
7178e72
Merge pull request #33 from BitBagCommerce/UC-26-customer-info
tomkalon Oct 1, 2025
71f69df
Merge pull request #32 from BitBagCommerce/UC-29-send-event
tomkalon Oct 1, 2025
bddfcbc
[UC-29] PHPstan and ECS fix
tomkalon Oct 1, 2025
1dd1b1e
Merge pull request #38 from BitBagCommerce/UC-29-send-event
tomkalon Oct 1, 2025
c6b7ea4
[UC-27] Add OpenApi decorator for User.com customer agreements and up…
tomkalon Oct 1, 2025
93c7545
Merge pull request #41 from BitBagCommerce/UC-27-api-v1
tomkalon Oct 2, 2025
25275f4
[UC-30] Introduce cookie queue handling and implement CookieFlusherSu…
tomkalon Oct 2, 2025
2540e30
Merge pull request #42 from BitBagCommerce/UC-30-fix-cookie
tomkalon Oct 2, 2025
7c25aaf
[UC-30] Introduce cookie queue handling and implement CookieFlusherSu…
tomkalon Oct 2, 2025
f42018e
Merge branch '1.1' into UC-30-fix-cookie
tomkalon Oct 2, 2025
9ea4488
Merge pull request #43 from BitBagCommerce/UC-30-fix-cookie
tomkalon Oct 2, 2025
3a3ce98
[UC-30] Introduce cookie queue handling and implement CookieFlusherSu…
tomkalon Oct 2, 2025
b67f1ef
[UC-30] Extend cookie handling with domain fallback and refactor even…
tomkalon Oct 2, 2025
d9ffadf
Merge branch '1.1' into UC-30-fix-cookie
tomkalon Oct 2, 2025
6a71e57
Merge pull request #44 from BitBagCommerce/UC-30-fix-cookie
tomkalon Oct 2, 2025
7eacc31
[UC-31] Remove unused mergeUsers call in CustomerWithKeyUpdater
tomkalon Oct 2, 2025
5ccbbc7
Merge pull request #46 from BitBagCommerce/bugfix/fix-different-email…
tomkalon Oct 2, 2025
f92f81a
Update documentation with User.com API usage, cookie domain handling,…
tomkalon Oct 3, 2025
3afeab1
Merge pull request #48 from BitBagCommerce/feature/manual
tomkalon Oct 3, 2025
ae3fed8
Update installation guide with encryption key/IV generation and async…
tomkalon Nov 25, 2025
89b0c7e
Update build workflow and Behat configuration
tomkalon Nov 25, 2025
b7bcc66
Add audit ignore rules to composer.json configuration
tomkalon Nov 25, 2025
89aecce
Merge pull request #51 from BitBagCommerce/fix/installation-manual
tomkalon Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,7 @@ jobs:
-
name: Output PHP version for Symfony CLI
run: php -v | head -n 1 | awk '{ print $2 }' > .php-version

-
name: Install certificates
run: symfony server:ca:install


-
name: Run Chrome Headless
run: google-chrome-stable --enable-automation --disable-background-networking --no-default-browser-check --no-first-run --disable-popup-blocking --disable-default-apps --allow-insecure-localhost --disable-translate --disable-extensions --no-sandbox --enable-features=Metal --headless --remote-debugging-port=9222 --window-size=2880,1800 --proxy-server='direct://' --proxy-bypass-list='*' http://127.0.0.1 > /dev/null 2>&1 &
Expand Down
2 changes: 1 addition & 1 deletion behat.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ default:

Behat\MinkExtension:
files_path: "%paths.base%/vendor/sylius/sylius/src/Sylius/Behat/Resources/fixtures/"
base_url: "https://127.0.0.1:8080/"
base_url: "http://127.0.0.1:8080/"
default_session: symfony
javascript_session: panther
sessions:
Expand Down
9 changes: 9 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
"dealerdirect/phpcodesniffer-composer-installer": false,
"phpstan/extension-installer": true,
"symfony/flex": true
},
"audit": {
"ignore": [
"PKSA-gs8r-6kz6-pp56",
"PKSA-gnn4-pxdg-q76m",
"PKSA-yhcn-xrg3-68b1",
"PKSA-2wrf-1xmk-1pky",
"PKSA-4g5g-4rkv-myqs"
]
}
},
"extra": {
Expand Down
3 changes: 2 additions & 1 deletion config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ parameters:
user_com.frontend_api_key: '%env(USER_COM_FRONTEND_API_KEY)%'
user_com.encryption_key: '%env(USER_COM_ENCRYPTION_KEY)%'
user_com.encryption_iv: '%env(USER_COM_ENCRYPTION_IV)%'

user_com.cookie_domain: '%env(USER_COM_COOKIE_DOMAIN)%'

twig:
globals:
user_com_frontend_api_key: '%user_com.frontend_api_key%'
Expand Down
4 changes: 4 additions & 0 deletions config/services/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@
<argument type="service" id="monolog.logger.user_com"/>
<argument type="service" id="bit_bag.sylius_user_com_plugin.manager.user_com_api_token_manager"/>
</service>

<service id="bit_bag.sylius_user_com_plugin.openapi.factory" class="BitBag\SyliusUserComPlugin\OpenApi\OpenApiFactory" decorates="api_platform.openapi.factory">
<argument type="service" id="bit_bag.sylius_user_com_plugin.openapi.factory.inner" />
</service>
</services>
</container>
8 changes: 8 additions & 0 deletions config/services/cookie.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>

<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="false" autoconfigure="false"/>
<service id="bit_bag.sylius_user_com_plugin.cookie.cookie_queue" class="BitBag\SyliusUserComPlugin\Cookie\CookieQueue" />
</services>
</container>
8 changes: 8 additions & 0 deletions config/services/event_subscriber.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,13 @@
<argument type="service" id="monolog.logger.user_com"/>
<tag name="kernel.event_subscriber"/>
</service>

<service id="bit_bag.sylius_user_com_plugin.event_subscriber.cookie_flusher_subscriber" class="BitBag\SyliusUserComPlugin\EventSubscriber\CookieFlusherSubscriber">
<argument type="service" id="bit_bag.sylius_user_com_plugin.cookie.cookie_queue" />
<tag name="kernel.event_listener"
event="kernel.response"
method="onResponse"
priority="0"/>
</service>
</services>
</container>
2 changes: 2 additions & 0 deletions config/services/manager.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
>
<argument type="service" id="request_stack"/>
<argument type="service" id="security.token_storage"/>
<argument type="service" id="bit_bag.sylius_user_com_plugin.cookie.cookie_queue"/>
<argument>%user_com.cookie_domain%</argument>
</service>

<service
Expand Down
5 changes: 5 additions & 0 deletions doc/adjustments.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ There is designated validator to check if request payload contains required fiel
If there will be any field added by User.com, you can adjust this validator to check if this field is present in request payload.
Also, in `BitBag\SyliusUserComPlugin\Assigner\AgreementsAssigner` you can adjust existing logic to assign agreements to customer in a way that suits your needs.

The endpoint is exposed in `Swagger` under `UserComAgreements`, making it easy to test and explore directly from the API documentation.

>If you use several channels, remember to select one of the available channels using the get method and parameters before using the API:
`?_channel_code=CHANNEL_CODE`

```php
public function assign(CustomerInterface $customer, array $agreements): void
{
Expand Down
8 changes: 6 additions & 2 deletions doc/functionalities.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ The **BitBagSyliusUserComPlugin** integrates **User.com** with Sylius-based stor
### 4. Event-Driven System
- Each customer interaction generates an **event**, which is stored and sent to **User.com** for automation and reporting.

### 5. Product Persistence & Feed Generation
### 5. Product Persistence
- **Persists products** within the system for accurate data reporting.
- Generates a **product feed** that can be used for marketing and analytics purposes.

### 6. Tag Manager Script Injection
- Allows users to **inject custom scripts** via **Tag Manager**.
- Enables integration with **third-party tracking tools**.

### 7. User information object
- you can use `user_com_customer_info` in browser console to check currently logged in customer data

### 8. Webhook Endpoint: Updating User Marketing Consents
- Exposes a dedicated endpoint for handling User.com webhooks (`UserComAgreements` in `Swagger`),
allowing the update of user marketing consents. By default, it manages the `subscribedToNewsletter` flag,
but the mechanism is fully extensible to support additional types of consents.”
12 changes: 12 additions & 0 deletions doc/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@
USER_COM_FRONTEND_API_KEY=""
USER_COM_ENCRYPTION_KEY=your-32-character-long-key
USER_COM_ENCRYPTION_IV=your-16-character-long-iv
USER_COM_COOKIE_DOMAIN=""
MESSENGER_USER_COM_ASYNCHRONOUS_DSN=""
```
- You can find the `USER_COM_FRONTEND_API_KEY` in the User.Com integration guide for `Google Tag Manager (Settings->Setup & Integrations)`.
- `USER_COM_ENCRYPTION_KEY` and `USER_COM_ENCRYPTION_IV` are required for cookie encryption.
- You can generate the encryption key and IV using the following command:
```bash
php -r '$key = bin2hex(random_bytes(16)); echo "USER_COM_ENCRYPTION_KEY=\"" . $key . "\"\n"; $iv = bin2hex(random_bytes(8)); echo "USER_COM_ENCRYPTION_IV=\"" . $iv . "\"\n";'
```
- `MESSENGER_USER_COM_ASYNCHRONOUS_DSN` is the DSN for the messenger transport that will handle asynchronous messages. You can use different transports like `doctrine://default`, `amqp://guest:guest@localhost:5672/%2f/messages`, etc.

- `USER_COM_COOKIE_DOMAIN` is optional, if not set, cookies will be set for the current domain.


3. Add plugin dependencies to `config/bundles.php` file. Make sure that none of the bundles are duplicated.
```php
return [
Expand Down
1 change: 0 additions & 1 deletion ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@
VisibilityRequiredFixer::class => ['*Spec.php'],
]);
};

2 changes: 1 addition & 1 deletion src/Api/UserApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function findUser(
$url = $this->getApiEndpointUrl(
$resource,
self::FIND_USER_ENDPOINT,
sprintf('?%s=%s', $field, $value),
sprintf('?%s=%s', $field, rawurlencode($value)),
);

return $this->request(
Expand Down
4 changes: 2 additions & 2 deletions src/Builder/Payload/CustomerPayloadBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public function build(string $email, ?CustomerInterface $customer = null, ?Addre
$payload = [
'custom_id' => strtolower($customer?->getEmail() ?? $email),
'email' => strtolower($customer?->getEmail() ?? $email),
'firstName' => $customer?->getFirstName(),
'lastName' => $customer?->getLastName(),
'first_name' => $customer?->getFirstName(),
'last_name' => $customer?->getLastName(),
'phone_number' => $customer?->getPhoneNumber(),
'country' => $address?->getCountryCode(),
'region' => $address?->getProvinceCode(),
Expand Down
33 changes: 33 additions & 0 deletions src/Cookie/CookieQueue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on [email protected].
*/

declare(strict_types=1);

namespace BitBag\SyliusUserComPlugin\Cookie;

use Symfony\Component\HttpFoundation\Cookie;

final class CookieQueue implements CookieQueueInterface
{
private array $queued = [];

public function queue(Cookie $cookie): void
{
$this->queued[] = $cookie;
}

/** @return Cookie[] */
public function pullAll(): array
{
$all = $this->queued;
$this->queued = [];

return $all;
}
}
22 changes: 22 additions & 0 deletions src/Cookie/CookieQueueInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on [email protected].
*/

declare(strict_types=1);

namespace BitBag\SyliusUserComPlugin\Cookie;

use Symfony\Component\HttpFoundation\Cookie;

interface CookieQueueInterface
{
public function queue(Cookie $cookie): void;

/** @return Cookie[] */
public function pullAll(): array;
}
31 changes: 31 additions & 0 deletions src/EventSubscriber/CookieFlusherSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on [email protected].
*/

declare(strict_types=1);

namespace BitBag\SyliusUserComPlugin\EventSubscriber;

use BitBag\SyliusUserComPlugin\Cookie\CookieQueueInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;

final class CookieFlusherSubscriber
{
public function __construct(private readonly CookieQueueInterface $queue)
{
}

public function onResponse(ResponseEvent $event): void
{
$response = $event->getResponse();

foreach ($this->queue->pullAll() as $cookie) {
$response->headers->setCookie($cookie);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface CustomerProfileUpdatedSubscriberInterface
'sylius_customer_profile' => 'customer_profile_update',
'sylius_admin_customer_update' => 'admin_customer_update',
'sylius_shop_account_profile_update' => 'shop_customer_update',
'sylius_shop_account_address_book_set_as_default' => 'shop_customer_default_address_update',
'sylius_shop_register' => 'customer_registration',
'sylius_shop_checkout_address' => 'customer_order_address_provided',
];
Expand Down
48 changes: 44 additions & 4 deletions src/Manager/CookieManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

namespace BitBag\SyliusUserComPlugin\Manager;

use BitBag\SyliusUserComPlugin\Cookie\CookieQueueInterface;
use Sylius\Component\Core\Model\AdminUserInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

Expand All @@ -20,6 +22,8 @@ final class CookieManager implements CookieManagerInterface
public function __construct(
private readonly RequestStack $requestStack,
private readonly TokenStorageInterface $tokenStorage,
private readonly CookieQueueInterface $queue,
private readonly ?string $cookieDomain = null,
) {
}

Expand All @@ -41,11 +45,29 @@ public function getUserComCookie(): ?string

public function setUserComCookie(string $value): void
{
$request = $this->requestStack->getCurrentRequest();
if (null === $request) {
return;
$cookie = Cookie::create(self::CHAT_COOKIE_NAME)
->withValue($value)
->withPath('/')
->withSecure(true)
->withExpires(new \DateTimeImmutable('+1 year'))
->withHttpOnly(false)
->withSameSite('lax');

if (null !== $this->cookieDomain && '' !== $this->cookieDomain) {
$cookie = $cookie->withDomain($this->cookieDomain);
} else {
$request = $this->requestStack->getCurrentRequest();
if (null === $request) {
return;
}

$domain = $this->getBaseDomain($request->getHost());
if (null !== $domain) {
$cookie = $cookie->withDomain($domain);
}
}
$request->cookies->set(self::CHAT_COOKIE_NAME, $value);

$this->queue->queue($cookie);
}

private function isShopUser(): bool
Expand All @@ -59,4 +81,22 @@ private function isShopUser(): bool

return true;
}

private function getBaseDomain(string $host): ?string
{
$host = (string) preg_replace('/:\d+$/', '', $host);

if ($host === 'localhost' || filter_var($host, \FILTER_VALIDATE_IP) !== false) {
return null;
}

$parts = explode('.', $host);
$count = count($parts);

if ($count >= 2) {
return '.' . $parts[$count - 2] . '.' . $parts[$count - 1];
}

return null;
}
}
4 changes: 2 additions & 2 deletions src/Manager/OrderUpdateManagerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
interface OrderUpdateManagerInterface
{
public const PRODUCT_EVENT_MAP = [
OrderInterface::STATE_NEW => 'purchase',
OrderInterface::STATE_FULFILLED => 'reservation',
OrderInterface::STATE_NEW => 'order',
OrderInterface::STATE_FULFILLED => 'purchase',
OrderInterface::STATE_CANCELLED => 'remove',
];

Expand Down
Loading