Skip to content

Commit

Permalink
Merge pull request #97 from Setono/dispatch-purchase-message
Browse files Browse the repository at this point in the history
Dispatch message when completing an order to later on send a purchase event to Google Analytics
  • Loading branch information
loevgaard authored Aug 9, 2023
2 parents 7e7ce32 + 2549a67 commit 1047859
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 119 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"psr/event-dispatcher": "^1.0",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"setono/composite-compiler-pass": "^1.1",
"setono/google-analytics-bundle": "^1.0.0-alpha.5",
"setono/google-analytics-bundle": "^1.0.0-alpha.6",
"setono/google-analytics-measurement-protocol": "^1.0@alpha",
"sylius/channel": "^1.0",
"sylius/channel-bundle": "^1.0",
Expand All @@ -38,6 +38,7 @@
"symfony/form": "^5.4 || ^6.0",
"symfony/http-foundation": "^5.4 || ^6.0",
"symfony/http-kernel": "^5.4 || ^6.0",
"symfony/messenger": "^5.4 || ^6.0",
"webmozart/assert": "^1.11"
},
"require-dev": {
Expand Down
14 changes: 13 additions & 1 deletion src/DependencyInjection/SetonoSyliusAnalyticsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

final class SetonoSyliusAnalyticsExtension extends AbstractResourceExtension
final class SetonoSyliusAnalyticsExtension extends AbstractResourceExtension implements PrependExtensionInterface
{
public function load(array $configs, ContainerBuilder $container): void
{
Expand Down Expand Up @@ -49,4 +50,15 @@ public function load(array $configs, ContainerBuilder $container): void
$container->registerForAutoconfiguration(VariantResolverInterface::class)
->addTag('setono_sylius_analytics.variant_resolver');
}

public function prepend(ContainerBuilder $container): void
{
$container->prependExtensionConfig('framework', [
'messenger' => [
'buses' => [
'setono_sylius_analytics.command_bus' => null,
],
],
]);
}
}
81 changes: 81 additions & 0 deletions src/EventSubscriber/DispatchSendPurchaseRequestSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusAnalyticsPlugin\EventSubscriber;

use Setono\GoogleAnalyticsBundle\Context\ClientIdContextInterface;
use Setono\GoogleAnalyticsMeasurementProtocol\Request\Body\Event\PurchaseEvent;
use Setono\SyliusAnalyticsPlugin\Message\Command\SendPurchaseEvent;
use Setono\SyliusAnalyticsPlugin\Resolver\Items\ItemsResolverInterface;
use Setono\SyliusAnalyticsPlugin\Util\FormatAmountTrait;
use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent;
use Sylius\Component\Core\Model\OrderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DelayStamp;
use Webmozart\Assert\Assert;

final class DispatchSendPurchaseRequestSubscriber implements EventSubscriberInterface
{
use FormatAmountTrait;

private ClientIdContextInterface $clientIdContext;

private MessageBusInterface $commandBus;

private ItemsResolverInterface $itemsResolver;

private int $delay;

public function __construct(ClientIdContextInterface $clientIdContext, MessageBusInterface $commandBus, ItemsResolverInterface $itemsResolver, int $delay = 43_200)
{
$this->clientIdContext = $clientIdContext;
$this->commandBus = $commandBus;
$this->itemsResolver = $itemsResolver;
$this->delay = $delay;
}

public static function getSubscribedEvents(): array
{
return [
'sylius.order.post_complete' => 'dispatch',
];
}

public function dispatch(ResourceControllerEvent $event): void
{
$order = $event->getSubject();
if (!$order instanceof OrderInterface) {
return;
}

$clientId = $this->clientIdContext->getClientId();
if (null === $clientId) {
return;
}

$channel = $order->getChannel();
Assert::notNull($channel);

$this->commandBus->dispatch(new Envelope(
new SendPurchaseEvent(
PurchaseEvent::create((string) $order->getNumber())
->setAffiliation(sprintf(
'%s (%s)',
(string) $channel->getName(),
(string) $order->getLocaleCode(),
))
->setValue(self::formatAmount($order->getTotal()))
->setCurrency($order->getCurrencyCode())
->setTax(self::formatAmount($order->getTaxTotal()))
->setShipping(self::formatAmount($order->getShippingTotal()))
->setItems($this->itemsResolver->resolveFromOrder($order)),
(int) $order->getId(),
$clientId
),
[new DelayStamp($this->delay)]
));
}
}
37 changes: 0 additions & 37 deletions src/EventSubscriber/UpdateClientIdOnOrderListener.php

This file was deleted.

9 changes: 9 additions & 0 deletions src/Message/Command/CommandInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusAnalyticsPlugin\Message\Command;

interface CommandInterface
{
}
23 changes: 23 additions & 0 deletions src/Message/Command/SendPurchaseEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusAnalyticsPlugin\Message\Command;

use Setono\GoogleAnalyticsMeasurementProtocol\Request\Body\Event\PurchaseEvent;

final class SendPurchaseEvent implements CommandInterface
{
public PurchaseEvent $event;

public int $orderId;

public string $clientId;

public function __construct(PurchaseEvent $event, int $orderId, string $clientId)
{
$this->event = $event;
$this->orderId = $orderId;
$this->clientId = $clientId;
}
}
81 changes: 81 additions & 0 deletions src/Message/Handler/SendPurchaseEventHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusAnalyticsPlugin\Message\Handler;

use Psr\EventDispatcher\EventDispatcherInterface;
use Setono\GoogleAnalyticsBundle\Event\ServerSideEvent;
use Setono\GoogleAnalyticsBundle\ValueObject\Property;
use Setono\SyliusAnalyticsPlugin\Message\Command\SendPurchaseEvent;
use Setono\SyliusAnalyticsPlugin\Repository\PropertyRepositoryInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\OrderCheckoutStates;
use Sylius\Component\Core\OrderPaymentStates;
use Sylius\Component\Order\Repository\OrderRepositoryInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Webmozart\Assert\Assert;

/**
* NOT final to make it easy to decorate and extend this handler
*
* todo this only works with gtag enabled and not tag manager
*/
class SendPurchaseEventHandler
{
private OrderRepositoryInterface $orderRepository;

private PropertyRepositoryInterface $propertyRepository;

private EventDispatcherInterface $eventDispatcher;

public function __construct(OrderRepositoryInterface $orderRepository, PropertyRepositoryInterface $propertyRepository, EventDispatcherInterface $eventDispatcher)
{
$this->orderRepository = $orderRepository;
$this->propertyRepository = $propertyRepository;
$this->eventDispatcher = $eventDispatcher;
}

public function __invoke(SendPurchaseEvent $message): void
{
$order = $this->orderRepository->find($message->orderId);
if (!$order instanceof OrderInterface) {
throw new UnrecoverableMessageHandlingException(sprintf(
'The order with id %d does not exist or is not an instance of %s',
$message->orderId,
OrderInterface::class
));
}

if (!$this->isEligible($order)) {
throw new \RuntimeException(sprintf('The order with id %d is not eligible for a Google Analytics purchase event to be sent', $message->orderId));
}

$channel = $order->getChannel();
Assert::notNull($channel);

$propertyEntities = $this->propertyRepository->findEnabledByChannel($channel);
if ([] === $propertyEntities) {
throw new UnrecoverableMessageHandlingException('You have not defined any Google Analytics properties');
}

$properties = [];
foreach ($propertyEntities as $propertyEntity) {
$measurementId = $propertyEntity->getMeasurementId();
$apiSecret = $propertyEntity->getApiSecret();

if (null === $measurementId || null === $apiSecret) {
continue;
}

$properties[] = new Property($measurementId, $apiSecret);
}

$this->eventDispatcher->dispatch(new ServerSideEvent($message->event, $message->clientId, $properties));
}

protected function isEligible(OrderInterface $order): bool
{
return in_array($order->getPaymentState(), [OrderPaymentStates::STATE_AUTHORIZED, OrderPaymentStates::STATE_PAID], true) && $order->getCheckoutState() === OrderCheckoutStates::STATE_COMPLETED;
}
}
17 changes: 0 additions & 17 deletions src/Model/OrderInterface.php

This file was deleted.

26 changes: 0 additions & 26 deletions src/Model/OrderTrait.php

This file was deleted.

1 change: 1 addition & 0 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<import resource="services/event_subscriber.xml"/>
<import resource="services/form.xml"/>
<import resource="services/menu.xml"/>
<import resource="services/message.xml"/>
<import resource="services/resolver.xml"/>
</imports>
</container>
6 changes: 4 additions & 2 deletions src/Resources/config/services/event_subscriber.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
</call>
</service>

<service id="setono_sylius_analytics.event_listener.update_client_id_on_order"
class="Setono\SyliusAnalyticsPlugin\EventSubscriber\UpdateClientIdOnOrderListener">
<service id="setono_sylius_analytics.event_subscriber.dispatch_send_purchase_request"
class="Setono\SyliusAnalyticsPlugin\EventSubscriber\DispatchSendPurchaseRequestSubscriber">
<argument type="service" id="setono_google_analytics.context.client_id"/>
<argument type="service" id="setono_sylius_analytics.command_bus"/>
<argument type="service" id="setono_sylius_analytics.resolver.items"/>

<tag name="kernel.event_subscriber"/>
</service>
Expand Down
15 changes: 15 additions & 0 deletions src/Resources/config/services/message.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>

<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="setono_sylius_analytics.message.handler.send_purchase_request"
class="Setono\SyliusAnalyticsPlugin\Message\Handler\SendPurchaseEventHandler">
<argument type="service" id="sylius.repository.order"/>
<argument type="service" id="setono_sylius_analytics.repository.property"/>
<argument type="service" id="event_dispatcher"/>

<tag name="messenger.message_handler"/>
</service>
</services>
</container>
19 changes: 0 additions & 19 deletions tests/Application/Model/Order.php

This file was deleted.

6 changes: 0 additions & 6 deletions tests/Application/config/packages/_sylius.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,3 @@ sylius_shop:

sylius_api:
enabled: true

sylius_order:
resources:
order:
classes:
model: Tests\Setono\SyliusAnalyticsPlugin\Application\Model\Order
Loading

0 comments on commit 1047859

Please sign in to comment.