<?php
/**
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* @package LnbSubscription
* @author Michael Lämmlein <michael.laemmlein@liebscher-bracht.com>
* @copyright ©2022 Liebscher & Bracht
* @license http://www.opensource.org/licenses/mit-license.php MIT-License
* @version 1.0.0
* @since 04.03.22
*/
declare(strict_types=1);
namespace Lnb\Shopware6\LnbSubscription\Subscriber\Subscription;
use Lnb\Shopware6\LnbSubscription\Event\OrderCreatedEvent;
use Lnb\Shopware6\LnbSubscription\Service\SubscriptionRenewService;
use Lnb\Shopware6\LnbSubscription\Struct\LineItemPayloadStruct;
use Psr\Log\LoggerInterface;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Checkout\Payment\Exception\InvalidOrderException;
use Shopware\Core\Checkout\Payment\Exception\PaymentProcessException;
use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException;
use Shopware\Core\Checkout\Payment\PaymentService;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\ShopwareException;
use Shopware\Core\Profiling\Profiler;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
readonly class SubscriptionSubscriber implements EventSubscriberInterface
{
public function __construct(
private SubscriptionRenewService $subscriptionRenewService,
private PaymentService $paymentService,
#[Target('order.repository')] private EntityRepository $orderRepository,
#[Target('order_line_item.repository')] private EntityRepository $orderLineItemRepository,
#[Target('lnb_subscription.logger')] private LoggerInterface $logger
) {
}
public static function getSubscribedEvents()
{
return [
OrderCreatedEvent::class => [
['onRenew'],
['addSubscriptionOrderIdToOrder'],
['addSubscriptionOrderIdToOrderLineItem'],
['onHandlePayment', -2],
]];
}
public function onRenew(OrderCreatedEvent $event): void
{
$this->subscriptionRenewService->renew($event->getSubscriptionOrderEntity(), $event->getContext());
$this->logger->info('OrderCreatedEvent renew subscription', [
'subscriptionOrderNumber' => $event->getSubscriptionOrderEntity()->getSubscriptionOrderNumber(),
'subscriptionOrderId' => $event->getSubscriptionOrderEntity()->getId(),
]);
}
public function onHandlePayment(OrderCreatedEvent $event): void
{
$orderId = $event->getOrderId();
$data = $event->getData();
$salesChannelContext = $event->getSalesChannelContext();
$finishUrl = null;
$errorUrl = null;
try {
$response = Profiler::trace(
'lnb-subscription-recurring-handle-payment',
function () use ($orderId, $data, $salesChannelContext, $finishUrl, $errorUrl): ?RedirectResponse {
return $this->paymentService->handlePaymentByOrder(
$orderId,
$data,
$salesChannelContext,
$finishUrl,
$errorUrl
);
}
);
$this->logger->info('OrderCreatedEvent::onHandlePayment: handlePaymentByOrder', [
'subscriptionOrderNumber' => $event->getSubscriptionOrderEntity()->getSubscriptionOrderNumber(),
'subscriptionOrderId' => $event->getSubscriptionOrderEntity()->getId(),
'payment_response_url' => $response?->getTargetUrl()
]);
} catch (PaymentProcessException | InvalidOrderException | UnknownPaymentMethodException $e) {
$this->logger->error(
'OrderCreatedEvent::onHandlePayment: An error occurred during processing the payment',
[
'subscriptionOrderNumber' => $event->getSubscriptionOrderEntity()->getSubscriptionOrderNumber(),
'subscriptionOrderId' => $event->getSubscriptionOrderEntity()->getId(),
'exceptionMessage' => $e->getMessage()
]
);
// @todo on error change payment method
throw $e;
}
// Try to finalize the payment
if (!$response) {
return;
}
$request = $this->getRequestForTransaction($response);
if (!$request->get('_sw_payment_token')) {
return;
}
try {
$tokenStruct = $this->paymentService->finalizeTransaction(
$request->get('_sw_payment_token'),
$request,
$salesChannelContext
);
$exception = $tokenStruct->getException();
if ($exception instanceof ShopwareException) {
throw $exception;
}
$this->logger->info('OrderCreatedEvent::onHandlePayment: finalizeTransaction', [
'subscriptionOrderNumber' => $event->getSubscriptionOrderEntity()->getSubscriptionOrderNumber(),
'subscriptionOrderId' => $event->getSubscriptionOrderEntity()->getId(),
'tokenStruct' => $tokenStruct
]);
} catch (PaymentProcessException | InvalidOrderException | UnknownPaymentMethodException $e) {
$this->logger->error(
'OrderCreatedEvent::onHandlePayment: An error occurred during finalize the payment',
[
'subscriptionOrderNumber' => $event->getSubscriptionOrderEntity()->getSubscriptionOrderNumber(),
'subscriptionOrderId' => $event->getSubscriptionOrderEntity()->getId(),
'exceptionMessage' => $e->getMessage()
]
);
throw $e;
}
}
public function addSubscriptionOrderIdToOrder(OrderCreatedEvent $event): void
{
$criteria = new Criteria([$event->getOrderId()]);
/** @var OrderEntity $order */
$order = $this->orderRepository->search($criteria, $event->getContext())->first();
$customFields = $order->getCustomFields();
$customFields['LnbSubscription']['lnbSubscriptionOrderId'] = $event->getSubscriptionOrderEntity()->getId();
$this->orderRepository->upsert([[
'id' => $order->getId(),
'customFields' => $customFields,
'lnbSubscriptions' => [
[
'id' => $event->getSubscriptionOrderEntity()->getId()
]
],
]], $event->getContext());
$this->logger->info('OrderCreatedEvent add subscriptionOrderId to order', [
'subscriptionOrderNumber' => $event->getSubscriptionOrderEntity()->getSubscriptionOrderNumber(),
'subscriptionOrderId' => $event->getSubscriptionOrderEntity()->getId(),
'orderId' => $order->getId(),
]);
}
public function addSubscriptionOrderIdToOrderLineItem(OrderCreatedEvent $event): void
{
$subscriptionOrderId = $event->getSubscriptionOrderEntity()->getId();
$criteria = new Criteria([$event->getOrderId()]);
$criteria->addAssociation('lineItems');
/** @var OrderEntity $order */
$order = $this->orderRepository->search($criteria, $event->getContext())->first();
$orderLineItems = $order->getLineItems()
->filter(function (OrderLineItemEntity $orderLineItem) use ($subscriptionOrderId): bool {
if (!isset($orderLineItem->getPayload()['LnbSubscription'])) {
return false;
}
$struct = new LineItemPayloadStruct($orderLineItem->getPayload()['LnbSubscription']);
if ($struct->getLnbSubscriptionOrderId() === $subscriptionOrderId) {
return true;
}
return false;
})
;
if (0 >= $orderLineItems->count()) {
return;
}
$update_data = [];
/** @var OrderLineItemEntity $orderLineItem */
foreach ($orderLineItems as $orderLineItem) {
$customFields = $orderLineItem->getCustomFields();
$customFields['LnbSubscription']['lnbSubscriptionOrderId'] = $subscriptionOrderId;
$update_data[] = [
'id' => $orderLineItem->getId(),
'customFields' => $customFields,
];
}
$this->orderLineItemRepository->update($update_data, $event->getContext());
$this->logger->info('OrderCreatedEvent add subscriptionOrderId to orderLineItems', [
'subscriptionOrderNumber' => $event->getSubscriptionOrderEntity()->getSubscriptionOrderNumber(),
'subscriptionOrderId' => $event->getSubscriptionOrderEntity()->getId(),
'update_data' => $update_data,
]);
}
private function getRequestForTransaction(RedirectResponse $response): Request
{
$query = (string) parse_url($response->getTargetUrl(), PHP_URL_QUERY);
parse_str($query, $result);
return new Request($result);
}
}