mirror of
https://github.com/vr-payment/shopware-6.git
synced 2026-06-04 19:03:01 +00:00
Release 7.3.0
This commit is contained in:
+6
-2
@@ -1,6 +1,10 @@
|
|||||||
|
# 7.3.0
|
||||||
|
- Headless storefront support
|
||||||
|
|
||||||
# 7.2.0
|
# 7.2.0
|
||||||
- Datenbanktabelle umbenannt, um Namenskonflikte mit älteren Plugins zu vermeiden.
|
- Renamed database table to avoid a naming conflict with legacy plugins
|
||||||
- Problem mit fehlgeschlagenen Rückerstattungen bei Zahlungen mit Rechnungen behoben.
|
- Fixed issue with refunds failing for payments using Invoice
|
||||||
|
- Fix to respect sort order of payment methods
|
||||||
|
|
||||||
# 7.1.6
|
# 7.1.6
|
||||||
- Improved rounding handling to force 2 decimal places
|
- Improved rounding handling to force 2 decimal places
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# 7.3.0
|
||||||
|
- Headless Storefront unterstützung
|
||||||
|
|
||||||
# 7.2.0
|
# 7.2.0
|
||||||
- Datenbanktabelle umbenannt, um Namenskonflikte mit älteren Plugins zu vermeiden.
|
- Datenbanktabelle umbenannt, um Namenskonflikte mit älteren Plugins zu vermeiden.
|
||||||
- Problem mit fehlgeschlagenen Rückerstattungen bei Zahlungen mit Rechnungen behoben.
|
- Problem mit fehlgeschlagenen Rückerstattungen bei Zahlungen mit Rechnungen behoben.
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ Please note that this plugin is for versions 6.5, 6.6 or 6.7. For the 6.4 plugin
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- For English documentation click [here](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.2.0/docs/en/documentation.html)
|
- For English documentation click [here](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.0/docs/en/documentation.html)
|
||||||
- Für die deutsche Dokumentation klicken Sie [hier](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.2.0/docs/de/documentation.html)
|
- Für die deutsche Dokumentation klicken Sie [hier](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.0/docs/de/documentation.html)
|
||||||
- Pour la documentation Française, cliquez [ici](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.2.0/docs/fr/documentation.html)
|
- Pour la documentation Française, cliquez [ici](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.0/docs/fr/documentation.html)
|
||||||
- Per la documentazione in tedesco, clicca [qui](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.2.0/docs/it/documentation.html)
|
- Per la documentazione in tedesco, clicca [qui](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.3.0/docs/it/documentation.html)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -59,5 +59,5 @@
|
|||||||
"vrpayment/sdk": "^4.0.0"
|
"vrpayment/sdk": "^4.0.0"
|
||||||
},
|
},
|
||||||
"type": "shopware-platform-plugin",
|
"type": "shopware-platform-plugin",
|
||||||
"version": "7.2.0"
|
"version": "7.3.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.2.0/">
|
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.3.0/">
|
||||||
Source
|
Source
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.2.0/">
|
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.3.0/">
|
||||||
Source
|
Source
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.2.0/">
|
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.3.0/">
|
||||||
Source
|
Source
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.2.0/">
|
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.3.0/">
|
||||||
Source
|
Source
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace VRPaymentPayment\Core\Api\Transaction\Service;
|
namespace VRPaymentPayment\Core\Api\Transaction\Service;
|
||||||
|
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shopware\Core\{
|
use Shopware\Core\{
|
||||||
@@ -79,6 +82,12 @@ class TransactionService
|
|||||||
*/
|
*/
|
||||||
private $settingsService;
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for storing pending transaction IDs across headless requests.
|
||||||
|
* @var CacheItemPoolInterface
|
||||||
|
*/
|
||||||
|
private CacheItemPoolInterface $cache;
|
||||||
|
|
||||||
const CARD_HOLDER_KEY = '1456765000789';
|
const CARD_HOLDER_KEY = '1456765000789';
|
||||||
const PSEUDO_CODE_KEY = '1485172176673';
|
const PSEUDO_CODE_KEY = '1485172176673';
|
||||||
const CARD_VALIDITY_KEY = '1456765711187';
|
const CARD_VALIDITY_KEY = '1456765711187';
|
||||||
@@ -91,16 +100,18 @@ class TransactionService
|
|||||||
* @param \Psr\Container\ContainerInterface $container
|
* @param \Psr\Container\ContainerInterface $container
|
||||||
* @param \VRPaymentPayment\Core\Util\LocaleCodeProvider $localeCodeProvider
|
* @param \VRPaymentPayment\Core\Util\LocaleCodeProvider $localeCodeProvider
|
||||||
* @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService
|
* @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService
|
||||||
|
* @param CacheItemPoolInterface $cache Cache for headless transaction persistence
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ContainerInterface $container,
|
ContainerInterface $container,
|
||||||
LocaleCodeProvider $localeCodeProvider,
|
LocaleCodeProvider $localeCodeProvider,
|
||||||
SettingsService $settingsService
|
SettingsService $settingsService,
|
||||||
)
|
CacheItemPoolInterface $cache
|
||||||
{
|
) {
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
$this->localeCodeProvider = $localeCodeProvider;
|
$this->localeCodeProvider = $localeCodeProvider;
|
||||||
$this->settingsService = $settingsService;
|
$this->settingsService = $settingsService;
|
||||||
|
$this->cache = $cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,8 +143,7 @@ class TransactionService
|
|||||||
public function create(
|
public function create(
|
||||||
PaymentTransactionStruct $transaction,
|
PaymentTransactionStruct $transaction,
|
||||||
SalesChannelContext $salesChannelContext
|
SalesChannelContext $salesChannelContext
|
||||||
): string
|
): string {
|
||||||
{
|
|
||||||
$criteria = new Criteria([$transaction->getOrderTransactionId()]);
|
$criteria = new Criteria([$transaction->getOrderTransactionId()]);
|
||||||
$criteria->addAssociation('order');
|
$criteria->addAssociation('order');
|
||||||
$orderTransaction = $this->container->get('order_transaction.repository')->search($criteria, $salesChannelContext->getContext())->first();
|
$orderTransaction = $this->container->get('order_transaction.repository')->search($criteria, $salesChannelContext->getContext())->first();
|
||||||
@@ -142,13 +152,28 @@ class TransactionService
|
|||||||
$settings = $this->settingsService->getSettings($salesChannelId);
|
$settings = $this->settingsService->getSettings($salesChannelId);
|
||||||
$apiClient = $settings->getApiClient();
|
$apiClient = $settings->getApiClient();
|
||||||
|
|
||||||
$transactionId = $_SESSION['transactionId'] ?? null;
|
// Get transaction ID from cache (headless) or session (storefront).
|
||||||
|
$transactionId = $this->getTransactionIdFromContext($salesChannelContext);
|
||||||
|
$pendingTransaction = null;
|
||||||
|
|
||||||
|
// Try to read the pending transaction if we have an ID stored.
|
||||||
if ($transactionId !== null) {
|
if ($transactionId !== null) {
|
||||||
$pendingTransaction = $this->read($_SESSION['transactionId'], $salesChannelId);
|
try {
|
||||||
|
$pendingTransaction = $this->read($transactionId, $salesChannelId);
|
||||||
|
// Verify it's still in PENDING state - otherwise we can't reuse it.
|
||||||
|
if ($pendingTransaction != null && $pendingTransaction->getState() !== TransactionState::PENDING) {
|
||||||
|
$pendingTransaction = null;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Transaction may have been deleted, expired, or is invalid - we'll create a new one.
|
||||||
|
$this->logger?->debug('Could not read pending transaction, will create new one: ' . $e->getMessage());
|
||||||
|
$pendingTransaction = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($transactionId === null || $pendingTransaction === null || $pendingTransaction->getState() !== TransactionState::PENDING) {
|
// Create a new transaction if we don't have a valid pending one.
|
||||||
unset($_SESSION['transactionId']);
|
if ($pendingTransaction === null) {
|
||||||
|
$this->clearTransactionIdFromContext($salesChannelContext);
|
||||||
$pendingTransactionId = $this->createPendingTransaction($salesChannelContext);
|
$pendingTransactionId = $this->createPendingTransaction($salesChannelContext);
|
||||||
$pendingTransaction = $this->read($pendingTransactionId, $salesChannelId);
|
$pendingTransaction = $this->read($pendingTransactionId, $salesChannelId);
|
||||||
}
|
}
|
||||||
@@ -161,6 +186,7 @@ class TransactionService
|
|||||||
$transaction
|
$transaction
|
||||||
));
|
));
|
||||||
$transactionPayloadClass->setLogger($this->logger);
|
$transactionPayloadClass->setLogger($this->logger);
|
||||||
|
$transactionPayloadClass->setTransactionId($pendingTransaction->getId());
|
||||||
$transactionPayload = $transactionPayloadClass->get($pendingTransaction->getVersion());
|
$transactionPayload = $transactionPayloadClass->get($pendingTransaction->getVersion());
|
||||||
|
|
||||||
$createdTransaction = $apiClient->getTransactionService()
|
$createdTransaction = $apiClient->getTransactionService()
|
||||||
@@ -170,7 +196,8 @@ class TransactionService
|
|||||||
$transaction,
|
$transaction,
|
||||||
$salesChannelContext->getContext(),
|
$salesChannelContext->getContext(),
|
||||||
$createdTransaction->getId(),
|
$createdTransaction->getId(),
|
||||||
$settings->getSpaceId()
|
$settings->getSpaceId(),
|
||||||
|
$salesChannelContext->getToken()
|
||||||
);
|
);
|
||||||
|
|
||||||
$redirectUrl = $this->container->get('router')->generate(
|
$redirectUrl = $this->container->get('router')->generate(
|
||||||
@@ -179,6 +206,16 @@ class TransactionService
|
|||||||
UrlGeneratorInterface::ABSOLUTE_URL
|
UrlGeneratorInterface::ABSOLUTE_URL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If the request comes from the Store API (headless), we should not redirect to a Storefront Twig page.
|
||||||
|
// Instead, we return the returnUrl so the headless client can handle the next steps (e.g. rendering the iframe).
|
||||||
|
$request = $this->container->get('request_stack')->getCurrentRequest();
|
||||||
|
if ($request) {
|
||||||
|
$routeScope = $request->attributes->get('_route_scope', []);
|
||||||
|
if (in_array('store-api', $routeScope, true)) {
|
||||||
|
$redirectUrl = $transaction->getReturnUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($settings->getIntegration() == Integration::PAYMENT_PAGE) {
|
if ($settings->getIntegration() == Integration::PAYMENT_PAGE) {
|
||||||
$redirectUrl = $apiClient->getTransactionPaymentPageService()
|
$redirectUrl = $apiClient->getTransactionPaymentPageService()
|
||||||
->paymentPageUrl($settings->getSpaceId(), $createdTransaction->getId());
|
->paymentPageUrl($settings->getSpaceId(), $createdTransaction->getId());
|
||||||
@@ -216,7 +253,8 @@ class TransactionService
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function createRecurringTransaction(TransactionCreate $sdkTransactionCreate, string $spaceId = ""): Transaction {
|
public function createRecurringTransaction(TransactionCreate $sdkTransactionCreate, string $spaceId = ""): Transaction
|
||||||
|
{
|
||||||
$settings = $this->settingsService->getSettings();
|
$settings = $this->settingsService->getSettings();
|
||||||
if (empty($spaceId)) {
|
if (empty($spaceId)) {
|
||||||
$spaceId = $settings->getSpaceId();
|
$spaceId = $settings->getSpaceId();
|
||||||
@@ -245,15 +283,21 @@ class TransactionService
|
|||||||
PaymentTransactionStruct $transaction,
|
PaymentTransactionStruct $transaction,
|
||||||
Context $context,
|
Context $context,
|
||||||
int $vrpaymentTransactionId,
|
int $vrpaymentTransactionId,
|
||||||
int $spaceId
|
int $spaceId,
|
||||||
): void
|
?string $token = null
|
||||||
{
|
): void {
|
||||||
$data = [
|
$customFields = [
|
||||||
'id' => $transaction->getOrderTransactionId(),
|
|
||||||
'customFields' => [
|
|
||||||
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID => $vrpaymentTransactionId,
|
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID => $vrpaymentTransactionId,
|
||||||
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID => $spaceId,
|
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID => $spaceId,
|
||||||
],
|
];
|
||||||
|
|
||||||
|
if ($token) {
|
||||||
|
$customFields[TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TOKEN] = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'id' => $transaction->getOrderTransactionId(),
|
||||||
|
'customFields' => $customFields,
|
||||||
];
|
];
|
||||||
$this->container->get('order_transaction.repository')->update([$data], $context);
|
$this->container->get('order_transaction.repository')->update([$data], $context);
|
||||||
}
|
}
|
||||||
@@ -271,8 +315,7 @@ class TransactionService
|
|||||||
Context $context,
|
Context $context,
|
||||||
string $paymentMethodId = null,
|
string $paymentMethodId = null,
|
||||||
string $salesChannelId = null
|
string $salesChannelId = null
|
||||||
): void
|
): void {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
$transactionId = $transaction->getId();
|
$transactionId = $transaction->getId();
|
||||||
@@ -351,7 +394,6 @@ class TransactionService
|
|||||||
|
|
||||||
$data = array_filter($data);
|
$data = array_filter($data);
|
||||||
$this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')->upsert([$data], $context);
|
$this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')->upsert([$data], $context);
|
||||||
|
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage());
|
$this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage());
|
||||||
}
|
}
|
||||||
@@ -402,7 +444,6 @@ class TransactionService
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw CartException::orderNotFound($orderId);
|
throw CartException::orderNotFound($orderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -450,7 +491,8 @@ class TransactionService
|
|||||||
return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')
|
return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')
|
||||||
->search(
|
->search(
|
||||||
(new Criteria())->addFilter(new EqualsFilter('transactionId', $transactionId))
|
(new Criteria())->addFilter(new EqualsFilter('transactionId', $transactionId))
|
||||||
->addAssociations(['refunds']), $context
|
->addAssociations(['refunds']),
|
||||||
|
$context
|
||||||
)
|
)
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
@@ -468,7 +510,8 @@ class TransactionService
|
|||||||
return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')
|
return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')
|
||||||
->search(
|
->search(
|
||||||
(new Criteria())->addFilter(new EqualsFilter('orderTransactionId', $orderTransactionId))
|
(new Criteria())->addFilter(new EqualsFilter('orderTransactionId', $orderTransactionId))
|
||||||
->addAssociations(['refunds']), $context
|
->addAssociations(['refunds']),
|
||||||
|
$context
|
||||||
)
|
)
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
@@ -485,7 +528,8 @@ class TransactionService
|
|||||||
{
|
{
|
||||||
return $this->container->get(RefundEntityDefinition::ENTITY_NAME . '.repository')
|
return $this->container->get(RefundEntityDefinition::ENTITY_NAME . '.repository')
|
||||||
->search(
|
->search(
|
||||||
(new Criteria())->addFilter(new EqualsFilter('transactionId', $transactionId)), $context
|
(new Criteria())->addFilter(new EqualsFilter('transactionId', $transactionId)),
|
||||||
|
$context
|
||||||
)
|
)
|
||||||
->getEntities();
|
->getEntities();
|
||||||
}
|
}
|
||||||
@@ -528,41 +572,34 @@ class TransactionService
|
|||||||
public function createPendingTransaction(SalesChannelContext $salesChannelContext, $event = null): int
|
public function createPendingTransaction(SalesChannelContext $salesChannelContext, $event = null): int
|
||||||
{
|
{
|
||||||
$expiredTransaction = true;
|
$expiredTransaction = true;
|
||||||
$transactionId = $_SESSION['transactionId'] ?? null;
|
// Get transaction ID from cache (headless) or session (storefront).
|
||||||
|
$transactionId = $this->getTransactionIdFromContext($salesChannelContext);
|
||||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||||
if (!$settings) {
|
if (!$settings) {
|
||||||
throw new \Exception('Space settings not configured');
|
throw new \Exception('Space settings not configured');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($transactionId) {
|
if ($transactionId) {
|
||||||
|
try {
|
||||||
$transactionService = $settings->getApiClient()->getTransactionService();
|
$transactionService = $settings->getApiClient()->getTransactionService();
|
||||||
$pendingTransaction = $transactionService->read($settings->getSpaceId(), $transactionId);
|
$pendingTransaction = $transactionService->read($settings->getSpaceId(), $transactionId);
|
||||||
if ($pendingTransaction->getState() === TransactionState::PENDING) {
|
if ($pendingTransaction->getState() === TransactionState::PENDING) {
|
||||||
$expiredTransaction = false;
|
$expiredTransaction = false;
|
||||||
}
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Transaction may have been deleted, expired, or is invalid - treat as expired.
|
||||||
|
$expiredTransaction = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$transactionId || $expiredTransaction) {
|
if (!$transactionId || $expiredTransaction) {
|
||||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||||
|
|
||||||
$customer = $salesChannelContext->getCustomer();
|
$customer = $salesChannelContext->getCustomer();
|
||||||
$lineItems = [];
|
if ($customer === null) {
|
||||||
if ($event) {
|
throw new \Exception('Customer is required to create a transaction');
|
||||||
if ($event instanceof CheckoutConfirmPageLoadedEvent) {
|
|
||||||
$cartLineItems = $event->getPage()->getCart()->getLineItems()->getElements();
|
|
||||||
foreach ($cartLineItems as $cartLineItem) {
|
|
||||||
if ($cartLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$lineItems[] = $this->createTempLineItem($cartLineItem);
|
|
||||||
}
|
|
||||||
} elseif ($event instanceof AccountEditOrderPageLoadedEvent) {
|
|
||||||
$order = $event->getPage()->getOrder();
|
|
||||||
foreach ($order->getLineItems() as $orderLineItem) {
|
|
||||||
$lineItems[] = $this->createTempLineItem($orderLineItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
$lineItems = $this->extractLineItems($event);
|
||||||
|
|
||||||
$customerId = "";
|
$customerId = "";
|
||||||
if ($customer->getGuest() === false) {
|
if ($customer->getGuest() === false) {
|
||||||
@@ -593,14 +630,16 @@ class TransactionService
|
|||||||
->setSuccessUrl($homeUrl . '?success')
|
->setSuccessUrl($homeUrl . '?success')
|
||||||
->setFailedUrl($homeUrl . '?fail');
|
->setFailedUrl($homeUrl . '?fail');
|
||||||
|
|
||||||
if($this->isSubscription($salesChannelContext)) {
|
if ($this->isSubscription($salesChannelContext)) {
|
||||||
$transactionPayload->setTokenizationMode(TokenizationMode::FORCE_CREATION);
|
$transactionPayload->setTokenizationMode(TokenizationMode::FORCE_CREATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
$transactionService = $settings->getApiClient()->getTransactionService();
|
$transactionService = $settings->getApiClient()->getTransactionService();
|
||||||
$transaction = $transactionService->create($settings->getSpaceId(), $transactionPayload);
|
$transaction = $transactionService->create($settings->getSpaceId(), $transactionPayload);
|
||||||
$transactionId = $transaction->getId();
|
$transactionId = $transaction->getId();
|
||||||
$_SESSION['transactionId'] = $transactionId;
|
|
||||||
|
// Store in cache and session for transaction reuse.
|
||||||
|
$this->storeTransactionIdInContext($salesChannelContext, $transactionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $transactionId;
|
return $transactionId;
|
||||||
@@ -611,7 +650,7 @@ class TransactionService
|
|||||||
* @param int $transactionId
|
* @param int $transactionId
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function updateTempTransaction(SalesChannelContext $salesChannelContext, int $transactionId): void
|
public function updateTempTransaction(SalesChannelContext $salesChannelContext, int $transactionId, array $lineItems = []): void
|
||||||
{
|
{
|
||||||
$pendingTransaction = new TransactionPending();
|
$pendingTransaction = new TransactionPending();
|
||||||
$pendingTransaction->setId($transactionId);
|
$pendingTransaction->setId($transactionId);
|
||||||
@@ -629,10 +668,50 @@ class TransactionService
|
|||||||
$pendingTransaction->setBillingAddress($billingAddress);
|
$pendingTransaction->setBillingAddress($billingAddress);
|
||||||
$pendingTransaction->setShippingAddress($shippingAddress);
|
$pendingTransaction->setShippingAddress($shippingAddress);
|
||||||
|
|
||||||
|
if (!empty($lineItems)) {
|
||||||
|
$pendingTransaction->setLineItems($lineItems);
|
||||||
|
}
|
||||||
|
|
||||||
$settings->getApiClient()->getTransactionService()
|
$settings->getApiClient()->getTransactionService()
|
||||||
->update($settings->getSpaceId(), $pendingTransaction);
|
->update($settings->getSpaceId(), $pendingTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts line items from the given source (Event or Cart).
|
||||||
|
*
|
||||||
|
* @param mixed $source
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function extractLineItems($source): array
|
||||||
|
{
|
||||||
|
$lineItems = [];
|
||||||
|
if ($source) {
|
||||||
|
if ($source instanceof CheckoutConfirmPageLoadedEvent) {
|
||||||
|
$cartLineItems = $source->getPage()->getCart()->getLineItems()->getElements();
|
||||||
|
foreach ($cartLineItems as $cartLineItem) {
|
||||||
|
if ($cartLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$lineItems[] = $this->createTempLineItem($cartLineItem);
|
||||||
|
}
|
||||||
|
} elseif ($source instanceof AccountEditOrderPageLoadedEvent) {
|
||||||
|
$order = $source->getPage()->getOrder();
|
||||||
|
foreach ($order->getLineItems() as $orderLineItem) {
|
||||||
|
$lineItems[] = $this->createTempLineItem($orderLineItem);
|
||||||
|
}
|
||||||
|
} elseif ($source instanceof \Shopware\Core\Checkout\Cart\Cart) {
|
||||||
|
$cartLineItems = $source->getLineItems()->getElements();
|
||||||
|
foreach ($cartLineItems as $cartLineItem) {
|
||||||
|
if ($cartLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$lineItems[] = $this->createTempLineItem($cartLineItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $lineItems;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ChargeAttempt|null $chargeAttempt
|
* @param ChargeAttempt|null $chargeAttempt
|
||||||
* @param string $descriptorKey
|
* @param string $descriptorKey
|
||||||
@@ -798,7 +877,8 @@ class TransactionService
|
|||||||
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
|
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function isSubscription(SalesChannelContext $salesChannelContext): bool {
|
private function isSubscription(SalesChannelContext $salesChannelContext): bool
|
||||||
|
{
|
||||||
$extensionName = 'subscription';
|
$extensionName = 'subscription';
|
||||||
if (class_exists(\Shopware\Commercial\Subscription\Framework\Struct\SubscriptionContextStruct::class)) {
|
if (class_exists(\Shopware\Commercial\Subscription\Framework\Struct\SubscriptionContextStruct::class)) {
|
||||||
$extensionName = SubscriptionContextStruct::SUBSCRIPTION_EXTENSION;
|
$extensionName = SubscriptionContextStruct::SUBSCRIPTION_EXTENSION;
|
||||||
@@ -815,7 +895,93 @@ class TransactionService
|
|||||||
*
|
*
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
private function round($value, $precision = 2): float {
|
private function round($value, $precision = 2): float
|
||||||
|
{
|
||||||
return \round($value, $precision);
|
return \round($value, $precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a cache key for the pending transaction ID.
|
||||||
|
* Uses customer ID for authenticated users, which works for both headless and storefront.
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function getPendingTransactionCacheKey(SalesChannelContext $salesChannelContext): ?string
|
||||||
|
{
|
||||||
|
$customer = $salesChannelContext->getCustomer();
|
||||||
|
if ($customer) {
|
||||||
|
return 'vrpn_pending_transaction_id_customer_' . $customer->getId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the stored pending transaction ID from cache or session.
|
||||||
|
* Uses customer ID as cache key for headless (stateless) support.
|
||||||
|
* Falls back to session for Storefront (stateful) compatibility.
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext
|
||||||
|
* @return int|null The transaction ID if found, otherwise null.
|
||||||
|
*/
|
||||||
|
private function getTransactionIdFromContext(SalesChannelContext $salesChannelContext): ?int
|
||||||
|
{
|
||||||
|
// Try cache first (for headless/API where session might not persist or be shared).
|
||||||
|
$cacheKey = $this->getPendingTransactionCacheKey($salesChannelContext);
|
||||||
|
if ($cacheKey) {
|
||||||
|
$item = $this->cache->getItem($cacheKey);
|
||||||
|
if ($item->isHit()) {
|
||||||
|
return (int) $item->get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to PHP session for traditional Storefront compatibility.
|
||||||
|
if (isset($_SESSION['transactionId'])) {
|
||||||
|
return (int) $_SESSION['transactionId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the pending transaction ID from cache and session.
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext
|
||||||
|
*/
|
||||||
|
private function clearTransactionIdFromContext(SalesChannelContext $salesChannelContext): void
|
||||||
|
{
|
||||||
|
// Clear from cache key.
|
||||||
|
$cacheKey = $this->getPendingTransactionCacheKey($salesChannelContext);
|
||||||
|
if ($cacheKey) {
|
||||||
|
$this->cache->deleteItem($cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear from session.
|
||||||
|
if (isset($_SESSION['transactionId'])) {
|
||||||
|
unset($_SESSION['transactionId']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the pending transaction ID in cache and session.
|
||||||
|
* This persists in the database (via cache) and works across all request types (Storefront & headless).
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext
|
||||||
|
* @param int $transactionId
|
||||||
|
*/
|
||||||
|
private function storeTransactionIdInContext(SalesChannelContext $salesChannelContext, int $transactionId): void
|
||||||
|
{
|
||||||
|
// Store in cache for headless.
|
||||||
|
$cacheKey = $this->getPendingTransactionCacheKey($salesChannelContext);
|
||||||
|
if ($cacheKey) {
|
||||||
|
$item = $this->cache->getItem($cacheKey);
|
||||||
|
$item->set($transactionId);
|
||||||
|
// Expire after 2 hours to avoid stale data (matching typical cart lifetime).
|
||||||
|
$item->expiresAfter(7200);
|
||||||
|
$this->cache->save($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store in session for Storefront.
|
||||||
|
$_SESSION['transactionId'] = $transactionId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace VRPaymentPayment\Core\Checkout\PaymentHandler;
|
namespace VRPaymentPayment\Core\Checkout\PaymentHandler;
|
||||||
|
|
||||||
@@ -119,8 +121,7 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
PaymentTransactionStruct $transaction,
|
PaymentTransactionStruct $transaction,
|
||||||
Context $context,
|
Context $context,
|
||||||
?Struct $validateStruct
|
?Struct $validateStruct
|
||||||
): RedirectResponse
|
): RedirectResponse {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
$orderTransactionId = $transaction->getOrderTransactionId();
|
$orderTransactionId = $transaction->getOrderTransactionId();
|
||||||
$orderTransaction = $this->orderTransactionRepository->search(
|
$orderTransaction = $this->orderTransactionRepository->search(
|
||||||
@@ -175,8 +176,7 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
Request $request,
|
Request $request,
|
||||||
PaymentTransactionStruct $transaction,
|
PaymentTransactionStruct $transaction,
|
||||||
Context $context
|
Context $context
|
||||||
): void
|
): void {
|
||||||
{
|
|
||||||
$orderTransactionId = $transaction->getOrderTransactionId();
|
$orderTransactionId = $transaction->getOrderTransactionId();
|
||||||
$orderTransaction = $this->orderTransactionRepository->search(
|
$orderTransaction = $this->orderTransactionRepository->search(
|
||||||
(new Criteria([$orderTransactionId]))
|
(new Criteria([$orderTransactionId]))
|
||||||
@@ -209,7 +209,7 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
$this->orderTransactionStateHandler->paid($orderTransaction->getId(), $context);
|
$this->orderTransactionStateHandler->paid($orderTransaction->getId(), $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = $request->getSession()->get('sw-context-token');
|
$token = $this->getContextToken($request);
|
||||||
if ($token) {
|
if ($token) {
|
||||||
$orderEntity = $this->pluginTransactionService->getOrderEntity(
|
$orderEntity = $this->pluginTransactionService->getOrderEntity(
|
||||||
$orderTransaction->getOrder()->getId(),
|
$orderTransaction->getOrder()->getId(),
|
||||||
@@ -222,6 +222,24 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
$salesChannelContext->getContext()->addState('do-cart-delete');
|
$salesChannelContext->getContext()->addState('do-cart-delete');
|
||||||
$this->logger->info('Clearing cart with token: ' . $token);
|
$this->logger->info('Clearing cart with token: ' . $token);
|
||||||
$this->cartPersister->delete($salesChannelContext->getToken(), $salesChannelContext);
|
$this->cartPersister->delete($salesChannelContext->getToken(), $salesChannelContext);
|
||||||
|
} else {
|
||||||
|
// Fallback: Try to get token from transaction custom fields (for Headless redirects)
|
||||||
|
$customFields = $orderTransaction->getCustomFields() ?? [];
|
||||||
|
$storedToken = $customFields[TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TOKEN] ?? null;
|
||||||
|
|
||||||
|
if ($storedToken) {
|
||||||
|
$this->logger->info('Clearing cart with stored token: ' . $storedToken);
|
||||||
|
$orderEntity = $this->pluginTransactionService->getOrderEntity(
|
||||||
|
$orderTransaction->getOrder()->getId(),
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
$salesChannelId = $orderEntity->getSalesChannelId();
|
||||||
|
$parameters = new SalesChannelContextServiceParameters($salesChannelId, $storedToken, originalContext: $context);
|
||||||
|
$salesChannelContext = $this->salesChannelContextService->get($parameters);
|
||||||
|
|
||||||
|
$salesChannelContext->getContext()->addState('do-cart-delete');
|
||||||
|
$this->cartPersister->delete($salesChannelContext->getToken(), $salesChannelContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +302,7 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
throw PaymentException::recurringInterrupted($newTransactionId, 'No orders found associated with the subscription.');
|
throw PaymentException::recurringInterrupted($newTransactionId, 'No orders found associated with the subscription.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$orders->sort(fn (OrderEntity $a, OrderEntity $b) => $a->getCreatedAt() <=> $b->getCreatedAt());
|
$orders->sort(fn(OrderEntity $a, OrderEntity $b) => $a->getCreatedAt() <=> $b->getCreatedAt());
|
||||||
/** @var OrderEntity|null $originalOrder */
|
/** @var OrderEntity|null $originalOrder */
|
||||||
$originalOrder = $orders->first();
|
$originalOrder = $orders->first();
|
||||||
|
|
||||||
@@ -296,7 +314,7 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
|
|
||||||
/** @var OrderTransactionEntity|null $originalTransaction */
|
/** @var OrderTransactionEntity|null $originalTransaction */
|
||||||
$originalTransaction = $originalTransactions->filter(
|
$originalTransaction = $originalTransactions->filter(
|
||||||
fn (OrderTransactionEntity $t) => $t->getStateMachineState()?->getTechnicalName() === OrderTransactionStates::STATE_PAID
|
fn(OrderTransactionEntity $t) => $t->getStateMachineState()?->getTechnicalName() === OrderTransactionStates::STATE_PAID
|
||||||
)->first();
|
)->first();
|
||||||
|
|
||||||
if ($originalTransaction === null) {
|
if ($originalTransaction === null) {
|
||||||
@@ -305,7 +323,8 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
|
|
||||||
$newOrderTransaction = $this->orderTransactionRepository->search(
|
$newOrderTransaction = $this->orderTransactionRepository->search(
|
||||||
(new Criteria([$newTransactionId]))
|
(new Criteria([$newTransactionId]))
|
||||||
->addAssociation('order'), $context
|
->addAssociation('order'),
|
||||||
|
$context
|
||||||
)->getEntities()->first();
|
)->getEntities()->first();
|
||||||
$orderNumber = $newOrderTransaction->getOrder()->getOrderNumber();
|
$orderNumber = $newOrderTransaction->getOrder()->getOrderNumber();
|
||||||
|
|
||||||
@@ -378,8 +397,7 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
// Update the new order transaction with the new transaction details
|
// Update the new order transaction with the new transaction details
|
||||||
$this->orderTransactionRepository->update([$data], $context);
|
$this->orderTransactionRepository->update([$data], $context);
|
||||||
$this->pluginTransactionService->upsert($newSdkTransaction, $context);
|
$this->pluginTransactionService->upsert($newSdkTransaction, $context);
|
||||||
}
|
} catch (\Throwable $e) {
|
||||||
catch (\Throwable $e) {
|
|
||||||
$errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage();
|
$errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage();
|
||||||
$this->logger->critical($errorMessage);
|
$this->logger->critical($errorMessage);
|
||||||
throw PaymentException::recurringInterrupted($transaction->getOrderTransactionId(), $errorMessage);
|
throw PaymentException::recurringInterrupted($transaction->getOrderTransactionId(), $errorMessage);
|
||||||
@@ -392,7 +410,8 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
* @param \VRPayment\Sdk\Model\Address $address The address model from the SDK.
|
* @param \VRPayment\Sdk\Model\Address $address The address model from the SDK.
|
||||||
* @return \VRPayment\Sdk\Model\AddressCreate The newly created AddressCreate instance.
|
* @return \VRPayment\Sdk\Model\AddressCreate The newly created AddressCreate instance.
|
||||||
*/
|
*/
|
||||||
private function addressCreateFromSdk(\VRPayment\Sdk\Model\Address $address): \VRPayment\Sdk\Model\AddressCreate {
|
private function addressCreateFromSdk(\VRPayment\Sdk\Model\Address $address): \VRPayment\Sdk\Model\AddressCreate
|
||||||
|
{
|
||||||
$addressCreate = new \VRPayment\Sdk\Model\AddressCreate;
|
$addressCreate = new \VRPayment\Sdk\Model\AddressCreate;
|
||||||
|
|
||||||
$addressCreate->setCity($address->getCity());
|
$addressCreate->setCity($address->getCity());
|
||||||
@@ -478,11 +497,12 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
*
|
*
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
private function round($value, $precision = 2): float {
|
private function round($value, $precision = 2): float
|
||||||
|
{
|
||||||
return \round($value, $precision);
|
return \round($value, $precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getContextToken(Request $request): string
|
private function getContextToken(Request $request): ?string
|
||||||
{
|
{
|
||||||
$headerContextToken = $request->headers->get('sw-context-token');
|
$headerContextToken = $request->headers->get('sw-context-token');
|
||||||
if ($headerContextToken) {
|
if ($headerContextToken) {
|
||||||
@@ -490,9 +510,10 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sessionContextToken = $request->getSession()->get("sw-context-token");
|
$sessionContextToken = $request->getSession()->get("sw-context-token");
|
||||||
if (!$sessionContextToken) {
|
if ($sessionContextToken) {
|
||||||
return $sessionContextToken;
|
return $sessionContextToken;
|
||||||
}
|
}
|
||||||
return Random::getAlphanumericString(32);
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\PaymentMethod\SalesChannel;
|
||||||
|
|
||||||
|
use Shopware\Core\Checkout\Payment\PaymentMethodDefinition;
|
||||||
|
use Shopware\Core\Checkout\Payment\SalesChannel\AbstractPaymentMethodRoute;
|
||||||
|
use Shopware\Core\Checkout\Payment\SalesChannel\PaymentMethodRouteResponse;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
|
||||||
|
use Shopware\Core\Framework\Log\Package;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use VRPaymentPayment\Core\Checkout\Service\PaymentMethodFilterService;
|
||||||
|
|
||||||
|
#[Package('checkout')]
|
||||||
|
/**
|
||||||
|
* This decorator intercepts the Store API payment method route to apply WhitelabelMachineName-specific filtering.
|
||||||
|
* It ensures that only payment methods valid for the current transaction (given currency, amount, etc.) are shown.
|
||||||
|
*/
|
||||||
|
class PaymentMethodRouteDecorator extends AbstractPaymentMethodRoute
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var AbstractPaymentMethodRoute
|
||||||
|
* The original route being decorated.
|
||||||
|
*/
|
||||||
|
private AbstractPaymentMethodRoute $decorated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PaymentMethodFilterService
|
||||||
|
* Service used to filter the payment methods based on WhitelabelMachineName API availability.
|
||||||
|
*/
|
||||||
|
private PaymentMethodFilterService $paymentMethodFilterService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AbstractPaymentMethodRoute $decorated
|
||||||
|
* @param PaymentMethodFilterService $paymentMethodFilterService
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
AbstractPaymentMethodRoute $decorated,
|
||||||
|
PaymentMethodFilterService $paymentMethodFilterService
|
||||||
|
) {
|
||||||
|
$this->decorated = $decorated;
|
||||||
|
$this->paymentMethodFilterService = $paymentMethodFilterService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decorated service.
|
||||||
|
*
|
||||||
|
* @return AbstractPaymentMethodRoute
|
||||||
|
*/
|
||||||
|
public function getDecorated(): AbstractPaymentMethodRoute
|
||||||
|
{
|
||||||
|
return $this->decorated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the payment methods and applies the WhitelabelMachineName filter to the result.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param SalesChannelContext $context
|
||||||
|
* @param Criteria $criteria
|
||||||
|
* @return PaymentMethodRouteResponse
|
||||||
|
*/
|
||||||
|
#[Route(
|
||||||
|
path: '/store-api/payment-method',
|
||||||
|
name: 'store-api.payment.method',
|
||||||
|
methods: ['GET', 'POST'],
|
||||||
|
defaults: ['_entity' => 'payment_method']
|
||||||
|
)]
|
||||||
|
public function load(Request $request, SalesChannelContext $context, Criteria $criteria): PaymentMethodRouteResponse
|
||||||
|
{
|
||||||
|
// Fetch the initial list of payment methods from the decorated service.
|
||||||
|
$response = $this->decorated->load($request, $context, $criteria);
|
||||||
|
|
||||||
|
$currentRoute = $request->attributes->get('_route');
|
||||||
|
if ($currentRoute === 'frontend.checkout.finish.page') {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$paymentMethods = $response->getPaymentMethods();
|
||||||
|
|
||||||
|
// Apply WhitelabelMachineName-specific filtering logic via the dedicated service.
|
||||||
|
$filteredCollection = $this->paymentMethodFilterService->filterPaymentMethods(
|
||||||
|
$paymentMethods,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return the filtered results as a new response.
|
||||||
|
return new PaymentMethodRouteResponse(
|
||||||
|
new EntitySearchResult(
|
||||||
|
'payment_method',
|
||||||
|
(int)$filteredCollection->count(),
|
||||||
|
$filteredCollection,
|
||||||
|
null,
|
||||||
|
$criteria,
|
||||||
|
$context->getContext()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\Service;
|
||||||
|
|
||||||
|
use Shopware\Core\Checkout\Cart\Cart;
|
||||||
|
use Shopware\Core\Checkout\Cart\LineItemFactoryRegistry;
|
||||||
|
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
|
||||||
|
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection;
|
||||||
|
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
|
||||||
|
use Shopware\Core\Checkout\Order\OrderEntity;
|
||||||
|
use Shopware\Core\Framework\Context;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
|
||||||
|
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use VRPaymentPayment\Core\Util\Payload\CustomProducts\CustomProductsLineItemTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service handles the reconstruction of a Shopware cart from an existing order.
|
||||||
|
* It ensures that line items, including those from complex plugins like 'Customized Products',
|
||||||
|
* are correctly re-added to a fresh cart.
|
||||||
|
*/
|
||||||
|
class CartRecoveryService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var CartService
|
||||||
|
* Shopware service for cart-related operations.
|
||||||
|
*/
|
||||||
|
private CartService $cartService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LineItemFactoryRegistry
|
||||||
|
* Registry to create Shopware LineItems from raw data.
|
||||||
|
*/
|
||||||
|
private LineItemFactoryRegistry $lineItemFactoryRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var EntityRepository
|
||||||
|
* Repository for accessing order data.
|
||||||
|
*/
|
||||||
|
private EntityRepository $orderRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var object|null
|
||||||
|
* Optional route service for adding customized products to the cart.
|
||||||
|
*/
|
||||||
|
private ?object $addCustomizedProductsRoute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CartService $cartService
|
||||||
|
* @param LineItemFactoryRegistry $lineItemFactoryRegistry
|
||||||
|
* @param EntityRepository $orderRepository
|
||||||
|
* @param object|null $addCustomizedProductsRoute
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
CartService $cartService,
|
||||||
|
LineItemFactoryRegistry $lineItemFactoryRegistry,
|
||||||
|
EntityRepository $orderRepository,
|
||||||
|
?object $addCustomizedProductsRoute = null
|
||||||
|
) {
|
||||||
|
$this->cartService = $cartService;
|
||||||
|
$this->lineItemFactoryRegistry = $lineItemFactoryRegistry;
|
||||||
|
$this->orderRepository = $orderRepository;
|
||||||
|
$this->addCustomizedProductsRoute = $addCustomizedProductsRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreates a cart based on the items in an existing order.
|
||||||
|
*
|
||||||
|
* @param OrderEntity $order The source order.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The current sales channel context.
|
||||||
|
* @return Cart The newly created and populated cart.
|
||||||
|
*/
|
||||||
|
public function recreateCartFromOrder(OrderEntity $order, SalesChannelContext $salesChannelContext): Cart
|
||||||
|
{
|
||||||
|
// Start with a clean slate by deleting any existing cart for the current session.
|
||||||
|
$this->cartService->deleteCart($salesChannelContext);
|
||||||
|
$cart = $this->cartService->createNew($salesChannelContext->getToken());
|
||||||
|
|
||||||
|
$orderItems = $order->getLineItems();
|
||||||
|
|
||||||
|
if ($orderItems === null) {
|
||||||
|
return $cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for Customized Products if the plugin logic is available.
|
||||||
|
if ($this->hasCustomProducts($orderItems) && $this->addCustomizedProductsRoute) {
|
||||||
|
$cart = $this->addCustomProducts($orderItems, $salesChannelContext, $cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var OrderLineItemEntity $orderLineItemEntity */
|
||||||
|
foreach ($orderItems as $orderLineItemEntity) {
|
||||||
|
$type = (string)$orderLineItemEntity->getType();
|
||||||
|
|
||||||
|
// Skip child items and complex types that should have been handled by specialized logic.
|
||||||
|
if ($type !== CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT || $orderLineItemEntity->getParentId() !== null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a standard product line item.
|
||||||
|
$lineItem = $this->lineItemFactoryRegistry->create([
|
||||||
|
'id' => $orderLineItemEntity->getId(),
|
||||||
|
'quantity' => (int)$orderLineItemEntity->getQuantity(),
|
||||||
|
'referencedId' => (string)$orderLineItemEntity->getReferencedId(),
|
||||||
|
'type' => $type,
|
||||||
|
], $salesChannelContext);
|
||||||
|
|
||||||
|
// Preserve payload data to ensure product options and other metadata are carried over.
|
||||||
|
$lineItemPayload = $orderLineItemEntity->getPayload();
|
||||||
|
if (!empty($lineItemPayload)) {
|
||||||
|
$lineItem->setPayload($lineItemPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the item to the cart.
|
||||||
|
$cart = $this->cartService->add($cart, $lineItem, $salesChannelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the order contains any line items belonging to the Customized Products plugin.
|
||||||
|
*
|
||||||
|
* @param OrderLineItemCollection $orderItems The items in the order.
|
||||||
|
* @return bool True if customized products are present.
|
||||||
|
*/
|
||||||
|
private function hasCustomProducts(OrderLineItemCollection $orderItems): bool
|
||||||
|
{
|
||||||
|
/** @var OrderLineItemEntity $orderItem */
|
||||||
|
foreach ($orderItems as $orderItem) {
|
||||||
|
if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized logic to re-add customized products to the cart via the plugin's own route.
|
||||||
|
*
|
||||||
|
* @param OrderLineItemCollection $orderItems All order items.
|
||||||
|
* @param SalesChannelContext $salesChannelContext Context.
|
||||||
|
* @param Cart $cart Current cart.
|
||||||
|
* @return Cart Updated cart.
|
||||||
|
*/
|
||||||
|
private function addCustomProducts(OrderLineItemCollection $orderItems, SalesChannelContext $salesChannelContext, Cart $cart): Cart
|
||||||
|
{
|
||||||
|
if (!$this->addCustomizedProductsRoute) {
|
||||||
|
return $cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var OrderLineItemEntity $orderItem */
|
||||||
|
foreach ($orderItems as $orderItem) {
|
||||||
|
if ($orderItem->getType() !== CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the main product associated with this customized product container.
|
||||||
|
$product = $this->getCustomProduct($orderItems, (string)$orderItem->getId());
|
||||||
|
if (!$product) continue;
|
||||||
|
|
||||||
|
// Gather the chosen options and their values.
|
||||||
|
$productOptions = $this->getCustomProductOptions($orderItems, (string)$orderItem->getId());
|
||||||
|
$optionValues = $this->getOptionValues($productOptions);
|
||||||
|
|
||||||
|
// Prepare the data bag for the specialized add-to-cart route.
|
||||||
|
$params = new RequestDataBag([
|
||||||
|
'customized-products-template' => new RequestDataBag([
|
||||||
|
'id' => (string)$orderItem->getReferencedId(),
|
||||||
|
'options' => new RequestDataBag($optionValues),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request = new Request([], [
|
||||||
|
'lineItems' => [
|
||||||
|
(string)$product->getReferencedId() => [
|
||||||
|
'quantity' => (int)$orderItem->getQuantity(),
|
||||||
|
'id' => (string)$product->getReferencedId(),
|
||||||
|
'type' => CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT,
|
||||||
|
'referencedId' => (string)$product->getReferencedId(),
|
||||||
|
'stackable' => (bool)$orderItem->getStackable(),
|
||||||
|
'removable' => (bool)$orderItem->getRemovable(),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Call the Customized Products plugin's internal logic to add the item with its complex configuration.
|
||||||
|
$this->addCustomizedProductsRoute->add($params, $request, $salesChannelContext, $cart);
|
||||||
|
|
||||||
|
// Re-fetch the cart to reflect changes made by the plugin route.
|
||||||
|
$cart = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the main product item within a customized product structure.
|
||||||
|
*
|
||||||
|
* @param OrderLineItemCollection $orderItems All order items.
|
||||||
|
* @param string $parentId The ID of the customized product container.
|
||||||
|
* @return OrderLineItemEntity|null The product line item entity.
|
||||||
|
*/
|
||||||
|
private function getCustomProduct(OrderLineItemCollection $orderItems, string $parentId): ?OrderLineItemEntity
|
||||||
|
{
|
||||||
|
/** @var OrderLineItemEntity $orderItem */
|
||||||
|
foreach ($orderItems as $orderItem) {
|
||||||
|
if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT && $orderItem->getParentId() === $parentId) {
|
||||||
|
return $orderItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gathers all option items for a customized product.
|
||||||
|
*
|
||||||
|
* @param OrderLineItemCollection $orderItems All items.
|
||||||
|
* @param string $parentId The ID of the customized product container.
|
||||||
|
* @return OrderLineItemEntity[] List of option entities.
|
||||||
|
*/
|
||||||
|
private function getCustomProductOptions(OrderLineItemCollection $orderItems, string $parentId): array
|
||||||
|
{
|
||||||
|
$options = [];
|
||||||
|
/** @var OrderLineItemEntity $orderItem */
|
||||||
|
foreach ($orderItems as $orderItem) {
|
||||||
|
if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS_OPTION && $orderItem->getParentId() === $parentId) {
|
||||||
|
$options[] = $orderItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a list of options into a data structure suitable for the Customized Products logic.
|
||||||
|
*
|
||||||
|
* @param OrderLineItemEntity[] $productOptions List of option entities.
|
||||||
|
* @return array Formatted option values.
|
||||||
|
*/
|
||||||
|
private function getOptionValues(array $productOptions): array
|
||||||
|
{
|
||||||
|
$optionValues = [];
|
||||||
|
foreach ($productOptions as $productOption) {
|
||||||
|
$payload = (array)$productOption->getPayload();
|
||||||
|
$optionType = (string)($payload['type'] ?? '');
|
||||||
|
|
||||||
|
switch ($optionType) {
|
||||||
|
case CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_IMAGE_UPLOAD:
|
||||||
|
case CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_FILE_UPLOAD:
|
||||||
|
$media = (array)($payload['media'] ?? []);
|
||||||
|
foreach ($media as $mediaItem) {
|
||||||
|
$optionValues[(string)$productOption->getReferencedId()] = new RequestDataBag([
|
||||||
|
'media' => new RequestDataBag([
|
||||||
|
(string)$mediaItem['filename'] => new RequestDataBag([
|
||||||
|
'id' => (string)$mediaItem['mediaId'],
|
||||||
|
'filename' => (string)$mediaItem['filename'],
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$optionValues[(string)$productOption->getReferencedId()] = new RequestDataBag([
|
||||||
|
'value' => (string)($payload['value'] ?? ''),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $optionValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a full order entity with necessary associations for recovery or display.
|
||||||
|
*
|
||||||
|
* @param string $orderId The ID of the order.
|
||||||
|
* @param Context $context The current system context.
|
||||||
|
* @return OrderEntity The order entity.
|
||||||
|
* @throws \Exception If the order is not found.
|
||||||
|
*/
|
||||||
|
public function getOrderEntity(string $orderId, Context $context): OrderEntity
|
||||||
|
{
|
||||||
|
$criteria = (new Criteria([$orderId]))
|
||||||
|
->addAssociation('lineItems.cover')
|
||||||
|
->addAssociation('transactions.paymentMethod')
|
||||||
|
->addAssociation('deliveries.shippingMethod');
|
||||||
|
|
||||||
|
/** @var OrderEntity|null $order */
|
||||||
|
$order = $this->orderRepository->search($criteria, $context)->get($orderId);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
throw new \Exception('Order not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\Service;
|
||||||
|
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
|
||||||
|
use VRPaymentPayment\Core\Settings\Service\SettingsService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service provides methods for retrieving invoice documents associated with WhitelabelMachineName transactions.
|
||||||
|
* It abstracts the API calls needed to fetch invoice data from the WhitelabelMachineName platform.
|
||||||
|
*/
|
||||||
|
class InvoiceService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SettingsService
|
||||||
|
* The service used to access sales channel specific settings.
|
||||||
|
*/
|
||||||
|
protected SettingsService $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TransactionService
|
||||||
|
* The service used to handle transaction-specific operations.
|
||||||
|
*/
|
||||||
|
protected TransactionService $transactionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
* @param TransactionService $transactionService
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
SettingsService $settingsService,
|
||||||
|
TransactionService $transactionService
|
||||||
|
) {
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->transactionService = $transactionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the invoice document metadata for a given order.
|
||||||
|
*
|
||||||
|
* @param string $orderId The Shopware order ID.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The current context.
|
||||||
|
*
|
||||||
|
* @return object The invoice document metadata (instance of \VRPayment\Sdk\Model\RenderedDocument).
|
||||||
|
*/
|
||||||
|
public function getInvoiceDocument(string $orderId, SalesChannelContext $salesChannelContext): object
|
||||||
|
{
|
||||||
|
// Retrieve valid settings for the current sales channel.
|
||||||
|
$settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId());
|
||||||
|
|
||||||
|
// Fetch the local transaction entity associated with the order.
|
||||||
|
$transactionEntity = $this->transactionService->getByOrderId($orderId, $salesChannelContext->getContext());
|
||||||
|
|
||||||
|
// Perform the API call to WhitelabelMachineName to get the invoice document metadata.
|
||||||
|
return $settings->getApiClient()->getTransactionService()->getInvoiceDocument(
|
||||||
|
(int)$settings->getSpaceId(),
|
||||||
|
(int)$transactionEntity->getTransactionId()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\Service;
|
||||||
|
|
||||||
|
use Shopware\Core\Framework\Uuid\Uuid;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Routing\RouterInterface;
|
||||||
|
use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
|
||||||
|
use VRPaymentPayment\Core\Settings\Options\Integration;
|
||||||
|
use VRPaymentPayment\Core\Settings\Service\SettingsService;
|
||||||
|
use VRPaymentPayment\Core\Checkout\Struct\PaymentConfigStruct;
|
||||||
|
use VRPayment\Sdk\Model\TransactionState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service provides the consolidated payment configuration needed for the frontend.
|
||||||
|
* it handles the generation of JS URLs and provides integration parameters for both Storefront and headless clients.
|
||||||
|
*/
|
||||||
|
class PaymentIntegrationService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var TransactionService
|
||||||
|
* Service to handle transaction-specific API calls.
|
||||||
|
*/
|
||||||
|
private TransactionService $transactionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SettingsService
|
||||||
|
* Service to retrieve sales channel specific settings.
|
||||||
|
*/
|
||||||
|
private SettingsService $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TransactionManagementService
|
||||||
|
* Service to manage transaction state.
|
||||||
|
*/
|
||||||
|
private TransactionManagementService $transactionManagementService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RouterInterface
|
||||||
|
* Shopware router for generating callback and redirect URLs.
|
||||||
|
*/
|
||||||
|
private RouterInterface $router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TransactionService $transactionService
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
* @param TransactionManagementService $transactionManagementService
|
||||||
|
* @param RouterInterface $router
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
TransactionService $transactionService,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
TransactionManagementService $transactionManagementService,
|
||||||
|
RouterInterface $router
|
||||||
|
) {
|
||||||
|
$this->transactionService = $transactionService;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->transactionManagementService = $transactionManagementService;
|
||||||
|
$this->router = $router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the payment configuration for a given transaction ID.
|
||||||
|
* This is used on the checkout confirm page before the order is created.
|
||||||
|
*
|
||||||
|
* @param int $transactionId The WhitelabelMachineName transaction ID.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @return PaymentConfigStruct The consolidated integration data.
|
||||||
|
*/
|
||||||
|
public function getConfigForTransaction(
|
||||||
|
int $transactionId,
|
||||||
|
SalesChannelContext $salesChannelContext
|
||||||
|
): PaymentConfigStruct {
|
||||||
|
$settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId());
|
||||||
|
|
||||||
|
// Fetch the transaction details from WhitelabelMachineName API.
|
||||||
|
$vrpaymentTransaction = $settings->getApiClient()->getTransactionService()->read(
|
||||||
|
$settings->getSpaceId(),
|
||||||
|
$transactionId
|
||||||
|
);
|
||||||
|
|
||||||
|
$javascriptUrl = $this->getTransactionJavaScriptUrl($settings, $transactionId);
|
||||||
|
|
||||||
|
$possiblePaymentMethods = $settings->getApiClient()
|
||||||
|
->getTransactionService()
|
||||||
|
->fetchPaymentMethods(
|
||||||
|
$settings->getSpaceId(),
|
||||||
|
$transactionId,
|
||||||
|
$settings->getIntegration()
|
||||||
|
);
|
||||||
|
|
||||||
|
$cartRecreateUrl = $this->router->generate(
|
||||||
|
'frontend.checkout.cart.page',
|
||||||
|
[],
|
||||||
|
UrlGeneratorInterface::ABSOLUTE_URL
|
||||||
|
);
|
||||||
|
|
||||||
|
$checkoutUrl = $this->router->generate(
|
||||||
|
'frontend.checkout.confirm.page',
|
||||||
|
[],
|
||||||
|
UrlGeneratorInterface::ABSOLUTE_URL
|
||||||
|
);
|
||||||
|
|
||||||
|
return (new PaymentConfigStruct())
|
||||||
|
->setIntegration((string)$settings->getIntegration())
|
||||||
|
->setJavascriptUrl($javascriptUrl)
|
||||||
|
->setDeviceJavascriptUrl($this->getDeviceJavascriptUrl((int)$settings->getSpaceId()))
|
||||||
|
->setTransactionPossiblePaymentMethods($possiblePaymentMethods)
|
||||||
|
->setTransactionId($transactionId)
|
||||||
|
->setSpaceId((int)$settings->getSpaceId())
|
||||||
|
->setCartRecreateUrl($cartRecreateUrl)
|
||||||
|
->setCheckoutUrl($checkoutUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the payment configuration for a given order.
|
||||||
|
*
|
||||||
|
* @param string $orderId The Shopware order ID.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @param string|null $cartRecreateUrl Optional override for the cart recreation URL.
|
||||||
|
* @param string|null $checkoutUrl Optional override for the checkout confirmation URL.
|
||||||
|
* @return PaymentConfigStruct The consolidated integration data.
|
||||||
|
*/
|
||||||
|
public function getPaymentConfig(
|
||||||
|
string $orderId,
|
||||||
|
SalesChannelContext $salesChannelContext,
|
||||||
|
?string $cartRecreateUrl = null,
|
||||||
|
?string $checkoutUrl = null
|
||||||
|
): PaymentConfigStruct {
|
||||||
|
// Retrieve settings and the transaction entity for the order.
|
||||||
|
$settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId());
|
||||||
|
$transactionEntity = $this->transactionService->getByOrderId($orderId, $salesChannelContext->getContext());
|
||||||
|
|
||||||
|
// Default to storefront URLs if no overrides are provided.
|
||||||
|
if ($cartRecreateUrl === null) {
|
||||||
|
$cartRecreateUrl = $this->router->generate(
|
||||||
|
'frontend.vrpayment.checkout.recreate-cart',
|
||||||
|
['orderId' => $orderId],
|
||||||
|
UrlGeneratorInterface::ABSOLUTE_URL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($checkoutUrl === null) {
|
||||||
|
$checkoutUrl = $this->router->generate(
|
||||||
|
'frontend.checkout.confirm.page',
|
||||||
|
[],
|
||||||
|
UrlGeneratorInterface::ABSOLUTE_URL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getConfigForTransaction((int)$transactionEntity->getTransactionId(), $salesChannelContext)
|
||||||
|
->setCartRecreateUrl($cartRecreateUrl)
|
||||||
|
->setCheckoutUrl($checkoutUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the JavaScript URL for the WhitelabelMachineName integration.
|
||||||
|
*
|
||||||
|
* @param mixed $settings The plugin settings.
|
||||||
|
* @param int $transactionId The transaction ID.
|
||||||
|
* @return string The absolute URL to the JavaScript component.
|
||||||
|
*/
|
||||||
|
private function getTransactionJavaScriptUrl($settings, int $transactionId): string
|
||||||
|
{
|
||||||
|
$javascriptUrl = '';
|
||||||
|
switch ($settings->getIntegration()) {
|
||||||
|
case Integration::IFRAME:
|
||||||
|
$javascriptUrl = $settings->getApiClient()->getTransactionIframeService()
|
||||||
|
->javascriptUrl($settings->getSpaceId(), $transactionId);
|
||||||
|
break;
|
||||||
|
case Integration::LIGHTBOX:
|
||||||
|
$javascriptUrl = $settings->getApiClient()->getTransactionLightboxService()
|
||||||
|
->javascriptUrl($settings->getSpaceId(), $transactionId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $javascriptUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the device tracking JavaScript URL.
|
||||||
|
*
|
||||||
|
* @param int $spaceId The WhitelabelMachineName space ID.
|
||||||
|
* @return string The tracking URL.
|
||||||
|
*/
|
||||||
|
private function getDeviceJavascriptUrl(int $spaceId): string
|
||||||
|
{
|
||||||
|
return 'https://gateway.vr-payment.de/s/' . $spaceId . '/payment/device.js?session=' . Uuid::randomHex();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\Service;
|
||||||
|
|
||||||
|
use Shopware\Core\Checkout\Payment\PaymentMethodCollection;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
|
||||||
|
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
|
||||||
|
use VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService;
|
||||||
|
use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
|
||||||
|
use VRPaymentPayment\Core\Checkout\PaymentHandler\VRPaymentPaymentHandler;
|
||||||
|
use VRPaymentPayment\Core\Settings\Service\SettingsService;
|
||||||
|
use VRPaymentPayment\Core\Util\PaymentMethodUtil;
|
||||||
|
use Shopware\Core\Framework\Struct\ArrayEntity;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service centralizes the logic for filtering WhitelabelMachineName payment methods.
|
||||||
|
* It ensures that only valid and available payment methods are displayed to the customer,
|
||||||
|
* based on the current transaction state and configured settings.
|
||||||
|
*/
|
||||||
|
class PaymentMethodFilterService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PaymentMethodConfigurationService
|
||||||
|
* Service to handle WhitelabelMachineName payment method configurations.
|
||||||
|
*/
|
||||||
|
private PaymentMethodConfigurationService $paymentMethodConfigurationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TransactionService
|
||||||
|
* Service to manage WhitelabelMachineName transactions via API.
|
||||||
|
*/
|
||||||
|
private TransactionService $transactionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SettingsService
|
||||||
|
* Service to retrieve plugin settings.
|
||||||
|
*/
|
||||||
|
private SettingsService $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PaymentMethodUtil
|
||||||
|
* Utility for payment method operations.
|
||||||
|
*/
|
||||||
|
private PaymentMethodUtil $paymentMethodUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var EntityRepository
|
||||||
|
* Repository for Shopware payment methods.
|
||||||
|
*/
|
||||||
|
private EntityRepository $paymentMethodRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TransactionManagementService
|
||||||
|
* Service to manage transaction state consistency.
|
||||||
|
*/
|
||||||
|
private TransactionManagementService $transactionManagementService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CartService
|
||||||
|
*/
|
||||||
|
private CartService $cartService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
* @param TransactionService $transactionService
|
||||||
|
* @param PaymentMethodConfigurationService $paymentMethodConfigurationService
|
||||||
|
* @param PaymentMethodUtil $paymentMethodUtil
|
||||||
|
* @param EntityRepository $paymentMethodRepository
|
||||||
|
* @param TransactionManagementService $transactionManagementService
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
SettingsService $settingsService,
|
||||||
|
TransactionService $transactionService,
|
||||||
|
PaymentMethodConfigurationService $paymentMethodConfigurationService,
|
||||||
|
PaymentMethodUtil $paymentMethodUtil,
|
||||||
|
EntityRepository $paymentMethodRepository,
|
||||||
|
TransactionManagementService $transactionManagementService,
|
||||||
|
CartService $cartService
|
||||||
|
) {
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->transactionService = $transactionService;
|
||||||
|
$this->paymentMethodConfigurationService = $paymentMethodConfigurationService;
|
||||||
|
$this->paymentMethodUtil = $paymentMethodUtil;
|
||||||
|
$this->paymentMethodRepository = $paymentMethodRepository;
|
||||||
|
$this->transactionManagementService = $transactionManagementService;
|
||||||
|
$this->cartService = $cartService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LoggerInterface $logger
|
||||||
|
*/
|
||||||
|
public function setLogger(LoggerInterface $logger): void
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the given collection of payment methods based on WhitelabelMachineName's availability logic.
|
||||||
|
*
|
||||||
|
* @param PaymentMethodCollection $paymentMethodCollection The initial collection of payment methods.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The current sales channel context.
|
||||||
|
* @param mixed $event Optional event that triggered the filtering.
|
||||||
|
* @return PaymentMethodCollection The filtered collection of payment methods.
|
||||||
|
*/
|
||||||
|
public function filterPaymentMethods(
|
||||||
|
PaymentMethodCollection $paymentMethodCollection,
|
||||||
|
SalesChannelContext $salesChannelContext,
|
||||||
|
$event = null
|
||||||
|
): PaymentMethodCollection {
|
||||||
|
// Fetch valid settings for the current sales channel.
|
||||||
|
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||||
|
|
||||||
|
// If settings are missing, remove all WhitelabelMachineName payment methods to prevent incorrect behavior.
|
||||||
|
if (is_null($settings)) {
|
||||||
|
return $this->removeVRPaymentPaymentMethods($paymentMethodCollection, $salesChannelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no customer, we cannot create a transaction or perform API-based filtering.
|
||||||
|
// This typically happens on non-checkout pages like the frontpage footer.
|
||||||
|
if ($salesChannelContext->getCustomer() === null) {
|
||||||
|
return $paymentMethodCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = $event;
|
||||||
|
if ($source === null) {
|
||||||
|
// In headless (Store API) flow, event is null. We explicitly fetch the cart to get line items.
|
||||||
|
$source = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a pending transaction exists in WhitelabelMachineName for correct filtering,
|
||||||
|
// using the transaction management service for state consistency.
|
||||||
|
$createdTransactionId = $this->transactionManagementService->getOrCreatePendingTransaction($salesChannelContext, $source);
|
||||||
|
|
||||||
|
// Update the temporary transaction if customer data has changed.
|
||||||
|
$this->transactionManagementService->updateTempTransactionIfNeeded($salesChannelContext, $createdTransactionId, $source);
|
||||||
|
|
||||||
|
// Fetch available payment method IDs from WhitelabelMachineName API for this transaction.
|
||||||
|
$allowedIds = $this->fetchAvailablePaymentMethodIds($settings, $createdTransactionId, $salesChannelContext);
|
||||||
|
|
||||||
|
// Return a new collection containing only allowed methods.
|
||||||
|
return $this->buildFilteredCollection($paymentMethodCollection, $allowedIds, $settings->getSpaceId(), $salesChannelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all WhitelabelMachineName-related payment methods from the collection.
|
||||||
|
*
|
||||||
|
* @param PaymentMethodCollection $paymentMethodCollection The collection to clean.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @return PaymentMethodCollection The cleaned collection.
|
||||||
|
*/
|
||||||
|
private function removeVRPaymentPaymentMethods(
|
||||||
|
PaymentMethodCollection $paymentMethodCollection,
|
||||||
|
SalesChannelContext $salesChannelContext
|
||||||
|
): PaymentMethodCollection {
|
||||||
|
$paymentMethodIds = $this->paymentMethodUtil->getVRPaymentPaymentMethodIds($salesChannelContext->getContext());
|
||||||
|
foreach ($paymentMethodIds as $paymentMethodId) {
|
||||||
|
$paymentMethodCollection->remove($paymentMethodId);
|
||||||
|
}
|
||||||
|
return $paymentMethodCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the list of allowed payment method IDs from the WhitelabelMachineName API.
|
||||||
|
*
|
||||||
|
* @param mixed $settings The plugin settings.
|
||||||
|
* @param int $createdTransactionId The WhitelabelMachineName transaction ID.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @return string[] Array of allowed payment method configuration IDs.
|
||||||
|
*/
|
||||||
|
private function fetchAvailablePaymentMethodIds(
|
||||||
|
$settings,
|
||||||
|
int $createdTransactionId,
|
||||||
|
SalesChannelContext $salesChannelContext
|
||||||
|
): array {
|
||||||
|
$transactionService = $settings->getApiClient()->getTransactionService();
|
||||||
|
$possiblePaymentMethods = $transactionService->fetchPaymentMethods(
|
||||||
|
$settings->getSpaceId(),
|
||||||
|
$createdTransactionId,
|
||||||
|
$settings->getIntegration()
|
||||||
|
);
|
||||||
|
|
||||||
|
$arrayOfPossibleMethods = [];
|
||||||
|
foreach ($possiblePaymentMethods as $possiblePaymentMethod) {
|
||||||
|
$arrayOfPossibleMethods[] = (string) $possiblePaymentMethod->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the allowed IDs in context extension for later use.
|
||||||
|
$salesChannelContext->getContext()->addExtension(
|
||||||
|
'possibleMethods',
|
||||||
|
new ArrayEntity(['ids' => $arrayOfPossibleMethods])
|
||||||
|
);
|
||||||
|
|
||||||
|
return $arrayOfPossibleMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a filtered PaymentMethodCollection based on allowed IDs.
|
||||||
|
*
|
||||||
|
* @param PaymentMethodCollection $paymentMethodCollection Original collection.
|
||||||
|
* @param string[] $allowedIds List of allowed configuration IDs.
|
||||||
|
* @param int $spaceId WhitelabelMachineName space ID.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @return PaymentMethodCollection The final collection.
|
||||||
|
*/
|
||||||
|
private function buildFilteredCollection(
|
||||||
|
PaymentMethodCollection $paymentMethodCollection,
|
||||||
|
array $allowedIds,
|
||||||
|
int $spaceId,
|
||||||
|
SalesChannelContext $salesChannelContext
|
||||||
|
): PaymentMethodCollection {
|
||||||
|
$paymentIds = [];
|
||||||
|
// Extract non-WhitelabelMachineName payment methods first.
|
||||||
|
foreach ($paymentMethodCollection as $paymentMethodCollectionItem) {
|
||||||
|
$isVRPaymentPM = VRPaymentPaymentHandler::class === $paymentMethodCollectionItem->getHandlerIdentifier();
|
||||||
|
if (!$isVRPaymentPM) {
|
||||||
|
$paymentIds[] = $paymentMethodCollectionItem->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedWLMethods = [];
|
||||||
|
// Fetch all WhitelabelMachineName payment method configurations for the space.
|
||||||
|
$paymentMethodConfigurations = $this->paymentMethodConfigurationService
|
||||||
|
->getAllPaymentMethodConfigurations($spaceId, $salesChannelContext->getContext());
|
||||||
|
|
||||||
|
// Check each configuration against the list of allowed IDs from WhitelabelMachineName API.
|
||||||
|
foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) {
|
||||||
|
if ($paymentMethodConfiguration->getPaymentMethod() === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pmId = $paymentMethodConfiguration->getPaymentMethod()->getId();
|
||||||
|
$pmConfigId = (string) $paymentMethodConfiguration->getPaymentMethodConfigurationId();
|
||||||
|
|
||||||
|
if (
|
||||||
|
$paymentMethodConfiguration->getSpaceId() === $spaceId
|
||||||
|
&& \in_array($pmConfigId, $allowedIds, true)
|
||||||
|
) {
|
||||||
|
$allowedWLMethods[] = $pmId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine non-WhitelabelMachineName and allowed WhitelabelMachineName payment methods.
|
||||||
|
$allPaymentIds = array_unique(array_merge($paymentIds, $allowedWLMethods));
|
||||||
|
$collection = new PaymentMethodCollection();
|
||||||
|
|
||||||
|
if (!empty($allPaymentIds)) {
|
||||||
|
$criteria = new Criteria($allPaymentIds);
|
||||||
|
$criteria->addFilter(new EqualsFilter('active', true));
|
||||||
|
$criteria->addFilter(
|
||||||
|
new EqualsFilter('salesChannels.id', $salesChannelContext->getSalesChannelId())
|
||||||
|
);
|
||||||
|
$criteria->addSorting(new FieldSorting('position', FieldSorting::ASCENDING));
|
||||||
|
|
||||||
|
// Re-fetch the entities to ensure we have valid objects with all associations.
|
||||||
|
$result = $this->paymentMethodRepository->search($criteria, $salesChannelContext->getContext());
|
||||||
|
/** @var \Shopware\Core\Checkout\Payment\PaymentMethodEntity $method */
|
||||||
|
foreach ($result->getEntities() as $method) {
|
||||||
|
if (!$collection->has((string)$method->getId())) {
|
||||||
|
// Attach the configuration to the payment method as an extension for Twig access.
|
||||||
|
foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) {
|
||||||
|
if ($paymentMethodConfiguration->getPaymentMethodId() === $method->getId()) {
|
||||||
|
$method->addExtension('vrpayment_config', $paymentMethodConfiguration);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$collection->add($method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\Service;
|
||||||
|
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
use Shopware\Core\Framework\Struct\ArrayEntity;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
|
||||||
|
use VRPaymentPayment\Core\Settings\Service\SettingsService;
|
||||||
|
use VRPayment\Sdk\Model\TransactionState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service manages the lifecycle of WhitelabelMachineName transactions and their state within the Shopware context.
|
||||||
|
* It provides methods to retrieve, create, and update transactions while ensuring state consistency.
|
||||||
|
*/
|
||||||
|
class TransactionManagementService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var TransactionService
|
||||||
|
* The service used to interact with the WhitelabelMachineName API for transaction operations.
|
||||||
|
*/
|
||||||
|
private TransactionService $transactionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SettingsService
|
||||||
|
* The service used to retrieve configuration settings for the current sales channel.
|
||||||
|
*/
|
||||||
|
private SettingsService $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CacheItemPoolInterface
|
||||||
|
* Cache for headless transaction persistence
|
||||||
|
*/
|
||||||
|
private CacheItemPoolInterface $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TransactionService $transactionService
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
* @param CacheItemPoolInterface $cache
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
TransactionService $transactionService,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
CacheItemPoolInterface $cache
|
||||||
|
) {
|
||||||
|
$this->transactionService = $transactionService;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an existing pending transaction ID from the context or creates a new one if necessary.
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext The current sales channel context.
|
||||||
|
* @param mixed $event Optional event context.
|
||||||
|
* @return int The WhitelabelMachineName transaction ID.
|
||||||
|
* @throws \Exception If settings are not configured.
|
||||||
|
*/
|
||||||
|
public function getOrCreatePendingTransaction(SalesChannelContext $salesChannelContext, $event = null): int
|
||||||
|
{
|
||||||
|
// Try to get the transaction ID from the current context state.
|
||||||
|
$transactionId = $this->getTransactionIdFromContext($salesChannelContext);
|
||||||
|
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||||
|
|
||||||
|
if (!$settings) {
|
||||||
|
throw new \Exception('Space settings not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
$expiredTransaction = true;
|
||||||
|
if ($transactionId) {
|
||||||
|
try {
|
||||||
|
// Verify if the transaction still exists and is in a PENDING state.
|
||||||
|
$pendingTransaction = $this->transactionService->read($transactionId, (string)$salesChannelContext->getSalesChannel()->getId());
|
||||||
|
if ($pendingTransaction->getState() === TransactionState::PENDING) {
|
||||||
|
$expiredTransaction = false;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// If the transaction cannot be read, we treat it as expired or invalid.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new transaction if none exists or the existing one is no longer valid.
|
||||||
|
if (!$transactionId || $expiredTransaction) {
|
||||||
|
$transactionId = (int)$this->transactionService->createPendingTransaction($salesChannelContext, $event);
|
||||||
|
$this->storeTransactionIdInContext($salesChannelContext, $transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transactionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the WhitelabelMachineName transaction if the customer context (address or currency) or line items have changed.
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext The current context.
|
||||||
|
* @param int $transactionId The WhitelabelMachineName transaction ID to update.
|
||||||
|
* @param mixed $event The event that triggered this update (optional).
|
||||||
|
*/
|
||||||
|
public function updateTempTransactionIfNeeded(SalesChannelContext $salesChannelContext, int $transactionId, $event = null): void
|
||||||
|
{
|
||||||
|
$ctx = $salesChannelContext->getContext();
|
||||||
|
|
||||||
|
/** @var ArrayEntity|null $ext */
|
||||||
|
$ext = $ctx->getExtension('checkoutState');
|
||||||
|
|
||||||
|
$oldAddressHash = $ext instanceof ArrayEntity ? (string)$ext->get('addressHash') : null;
|
||||||
|
$oldCurrency = $ext instanceof ArrayEntity ? (string)$ext->get('currency') : null;
|
||||||
|
$oldLineItemHash = $ext instanceof ArrayEntity ? (string)$ext->get('lineItemHash') : null;
|
||||||
|
|
||||||
|
$customer = $salesChannelContext->getCustomer();
|
||||||
|
$addressHash = $customer ? md5(json_encode((array) $customer)) : null;
|
||||||
|
$currency = (string)$salesChannelContext->getCurrency()->getIsoCode();
|
||||||
|
|
||||||
|
$lineItems = $this->transactionService->extractLineItems($event);
|
||||||
|
$lineItemHash = !empty($lineItems) ? md5(json_encode($lineItems)) : $oldLineItemHash;
|
||||||
|
|
||||||
|
$needsUpdate = ($oldAddressHash !== $addressHash)
|
||||||
|
|| ($oldCurrency !== $currency)
|
||||||
|
|| ($oldLineItemHash !== $lineItemHash);
|
||||||
|
|
||||||
|
if ($needsUpdate) {
|
||||||
|
// Update the transaction in WhitelabelMachineName to reflect current cart and customer data.
|
||||||
|
if ($transactionId) {
|
||||||
|
$this->transactionService->updateTempTransaction($salesChannelContext, $transactionId, $lineItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear payment method cache as options might have changed due to address/currency change.
|
||||||
|
$ctx->addExtension('possibleMethods', new ArrayEntity(['ids' => []]));
|
||||||
|
|
||||||
|
// Persist the new state hash in the context.
|
||||||
|
$ctx->addExtension(
|
||||||
|
'checkoutState',
|
||||||
|
new ArrayEntity([
|
||||||
|
'transactionId' => $transactionId,
|
||||||
|
'addressHash' => $addressHash,
|
||||||
|
'currency' => $currency,
|
||||||
|
'lineItemHash' => $lineItemHash,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the stored WhitelabelMachineName transaction ID from the context, cache, or session.
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @return int|null The transaction ID if found, otherwise null.
|
||||||
|
*/
|
||||||
|
public function getTransactionIdFromContext(SalesChannelContext $salesChannelContext): ?int
|
||||||
|
{
|
||||||
|
/** @var ArrayEntity|null $ext */
|
||||||
|
$ext = $salesChannelContext->getContext()->getExtension('checkoutState');
|
||||||
|
if ($ext instanceof ArrayEntity && $ext->get('transactionId')) {
|
||||||
|
return (int) $ext->get('transactionId');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get from cache (headless support).
|
||||||
|
$customer = $salesChannelContext->getCustomer();
|
||||||
|
if ($customer) {
|
||||||
|
$cacheKey = 'vrpn_pending_transaction_id_customer_' . $customer->getId();
|
||||||
|
$item = $this->cache->getItem($cacheKey);
|
||||||
|
if ($item->isHit()) {
|
||||||
|
return (int) $item->get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to PHP session for traditional Storefront compatibility.
|
||||||
|
if (isset($_SESSION['transactionId'])) {
|
||||||
|
return (int) $_SESSION['transactionId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists the transaction ID in the context state, cache, and session.
|
||||||
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @param int $transactionId The transaction ID to store.
|
||||||
|
*/
|
||||||
|
private function storeTransactionIdInContext(SalesChannelContext $salesChannelContext, int $transactionId): void
|
||||||
|
{
|
||||||
|
$ctx = $salesChannelContext->getContext();
|
||||||
|
/** @var ArrayEntity|null $ext */
|
||||||
|
$ext = $ctx->getExtension('checkoutState');
|
||||||
|
|
||||||
|
$data = $ext instanceof ArrayEntity ? $ext->all() : [];
|
||||||
|
$data['transactionId'] = $transactionId;
|
||||||
|
|
||||||
|
// Store in context extension for stateless (headless) support within the request.
|
||||||
|
$ctx->addExtension('checkoutState', new ArrayEntity($data));
|
||||||
|
|
||||||
|
// Store in cache for persistent headless support.
|
||||||
|
$customer = $salesChannelContext->getCustomer();
|
||||||
|
if ($customer) {
|
||||||
|
$cacheKey = 'vrpn_pending_transaction_id_customer_' . $customer->getId();
|
||||||
|
$item = $this->cache->getItem($cacheKey);
|
||||||
|
$item->set($transactionId);
|
||||||
|
$item->expiresAfter(7200);
|
||||||
|
$this->cache->save($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync with PHP session for stateful Storefront support.
|
||||||
|
$_SESSION['transactionId'] = $transactionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\StoreApi\Route;
|
||||||
|
|
||||||
|
use Shopware\Core\Framework\Log\Package;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use VRPaymentPayment\Core\Checkout\Service\CartRecoveryService;
|
||||||
|
|
||||||
|
#[Package('checkout')]
|
||||||
|
#[Route(defaults: ['_routeScope' => ['store-api']])]
|
||||||
|
/**
|
||||||
|
* This Store API route allows headless clients to recreate a cart from an existing order.
|
||||||
|
* This is particularly useful for 'Try Again' scenarios if a payment fails.
|
||||||
|
*/
|
||||||
|
class CartRecoveryRoute
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var CartRecoveryService
|
||||||
|
* Service to handle the logic of reconstructing a cart from order items.
|
||||||
|
*/
|
||||||
|
private CartRecoveryService $cartRecoveryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CartRecoveryService $cartRecoveryService
|
||||||
|
*/
|
||||||
|
public function __construct(CartRecoveryService $cartRecoveryService)
|
||||||
|
{
|
||||||
|
$this->cartRecoveryService = $cartRecoveryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreates a cart based on the provided order ID.
|
||||||
|
*
|
||||||
|
* @param string $orderId The ID of the order to recover the cart from.
|
||||||
|
* @param Request $request The incoming request.
|
||||||
|
* @param SalesChannelContext $context The current sales channel context.
|
||||||
|
* @return JsonResponse A JSON response containing either the new cart data or an error message.
|
||||||
|
*/
|
||||||
|
#[Route(
|
||||||
|
path: '/store-api/vrpayment/checkout/recreate-cart/{orderId}',
|
||||||
|
name: 'store-api.vrpayment.checkout.recreate-cart',
|
||||||
|
methods: ['POST']
|
||||||
|
)]
|
||||||
|
public function recreate(string $orderId, Request $request, SalesChannelContext $context): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Fetch the order entity.
|
||||||
|
$order = $this->cartRecoveryService->getOrderEntity($orderId, $context->getContext());
|
||||||
|
|
||||||
|
// Security check: ensure the order belongs to the current sales channel.
|
||||||
|
if ($order->getSalesChannelId() !== $context->getSalesChannelId()) {
|
||||||
|
return new JsonResponse(['error' => 'Sales channel mismatch'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the cart reconstruction.
|
||||||
|
$cart = $this->cartRecoveryService->recreateCartFromOrder($order, $context);
|
||||||
|
|
||||||
|
// Return the reconstructed cart data.
|
||||||
|
return new JsonResponse($cart);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Handle any exceptions during the process.
|
||||||
|
return new JsonResponse(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\StoreApi\Route;
|
||||||
|
|
||||||
|
use Shopware\Core\Framework\Log\Package;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use VRPaymentPayment\Core\Checkout\Service\InvoiceService;
|
||||||
|
|
||||||
|
#[Package('checkout')]
|
||||||
|
#[Route(defaults: ['_routeScope' => ['store-api']])]
|
||||||
|
/**
|
||||||
|
* This Store API route provides access to invoice documents for headless clients.
|
||||||
|
*/
|
||||||
|
class InvoiceRoute
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var InvoiceService
|
||||||
|
* Service to handle the retrieval of invoice documents from WhitelabelMachineName.
|
||||||
|
*/
|
||||||
|
private InvoiceService $invoiceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InvoiceService $invoiceService
|
||||||
|
*/
|
||||||
|
public function __construct(InvoiceService $invoiceService)
|
||||||
|
{
|
||||||
|
$this->invoiceService = $invoiceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the invoice document metadata and content for a given order.
|
||||||
|
*
|
||||||
|
* @param string $orderId The ID of the order.
|
||||||
|
* @param Request $request The incoming request.
|
||||||
|
* @param SalesChannelContext $context The current sales channel context.
|
||||||
|
* @return JsonResponse A JSON response containing the invoice title, MIME type, and base64-encoded data.
|
||||||
|
*/
|
||||||
|
#[Route(
|
||||||
|
path: '/store-api/vrpayment/account/order/invoice/{orderId}',
|
||||||
|
name: 'store-api.vrpayment.account.order.invoice',
|
||||||
|
methods: ['GET']
|
||||||
|
)]
|
||||||
|
public function load(string $orderId, Request $request, SalesChannelContext $context): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Retrieve the invoice document via the dedicated service.
|
||||||
|
$invoice = $this->invoiceService->getInvoiceDocument($orderId, $context);
|
||||||
|
|
||||||
|
// Structure the response for headless consumption.
|
||||||
|
return new JsonResponse([
|
||||||
|
'title' => (string)$invoice->getTitle(),
|
||||||
|
'mimeType' => (string)$invoice->getMimeType(),
|
||||||
|
'data' => (string)$invoice->getData(), // Base64 encoded
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Handle errors (e.g., order not found or API failure).
|
||||||
|
return new JsonResponse(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\StoreApi\Route;
|
||||||
|
|
||||||
|
use Shopware\Core\Framework\Log\Package;
|
||||||
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use VRPaymentPayment\Core\Checkout\Service\PaymentIntegrationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This route provides transaction-specific configuration for a given order.
|
||||||
|
* It is primarily used by headless clients to initialize the WhitelabelMachineName payment iframe or lightbox.
|
||||||
|
*/
|
||||||
|
#[Package('checkout')]
|
||||||
|
#[Route(defaults: ['_routeScope' => ['store-api']])]
|
||||||
|
class WhitelabelMachineNameTransactionInfoRoute
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PaymentIntegrationService
|
||||||
|
* The service responsible for generating the unified payment configuration.
|
||||||
|
*/
|
||||||
|
private PaymentIntegrationService $paymentIntegrationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PaymentIntegrationService $paymentIntegrationService
|
||||||
|
*/
|
||||||
|
public function __construct(PaymentIntegrationService $paymentIntegrationService)
|
||||||
|
{
|
||||||
|
$this->paymentIntegrationService = $paymentIntegrationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the payment configuration for a specific order.
|
||||||
|
*
|
||||||
|
* @param string $orderId The Shopware order ID.
|
||||||
|
* @param Request $request The incoming request object.
|
||||||
|
* @param SalesChannelContext $context The current sales channel context.
|
||||||
|
* @return JsonResponse JSON response containing the PaymentConfigStruct data.
|
||||||
|
*/
|
||||||
|
#[Route(
|
||||||
|
path: '/store-api/vrpayment/transaction/info/{orderId}',
|
||||||
|
name: 'store-api.vrpayment.transaction.info',
|
||||||
|
methods: ['GET']
|
||||||
|
)]
|
||||||
|
public function load(string $orderId, Request $request, SalesChannelContext $context): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Retrieve the payment configuration using the common service.
|
||||||
|
// This ensures logic parity between Storefront and Store API.
|
||||||
|
$config = $this->paymentIntegrationService->getPaymentConfig($orderId, $context);
|
||||||
|
|
||||||
|
// Return the configuration as a JSON response for the headless client.
|
||||||
|
return new JsonResponse($config);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// In case of error, return a 400 response with the error message.
|
||||||
|
return new JsonResponse(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace VRPaymentPayment\Core\Checkout\Struct;
|
||||||
|
|
||||||
|
use Shopware\Core\Framework\Struct\Struct;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This struct encapsulates all the configuration data required to initialize a WhitelabelMachineName payment integration.
|
||||||
|
*/
|
||||||
|
class PaymentConfigStruct extends Struct
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* The type of integration (e.g., 'iframe' or 'lightbox').
|
||||||
|
*/
|
||||||
|
protected string $integration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* The absolute URL to the WhitelabelMachineName JavaScript component for transaction handling.
|
||||||
|
*/
|
||||||
|
protected string $javascriptUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* The absolute URL to the WhitelabelMachineName JavaScript component for device tracking.
|
||||||
|
*/
|
||||||
|
protected string $deviceJavascriptUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
* A list of possible payment methods for the current transaction.
|
||||||
|
*/
|
||||||
|
protected array $transactionPossiblePaymentMethods;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
* The unique ID of the WhitelabelMachineName transaction.
|
||||||
|
*/
|
||||||
|
protected int $transactionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
* The unique ID of the WhitelabelMachineName space.
|
||||||
|
*/
|
||||||
|
protected int $spaceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
* The URL to redirect to if the cart needs to be recreated (e.g., after an error).
|
||||||
|
*/
|
||||||
|
protected ?string $cartRecreateUrl = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
* The URL to the checkout confirmation page.
|
||||||
|
*/
|
||||||
|
protected ?string $checkoutUrl = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getIntegration(): string
|
||||||
|
{
|
||||||
|
return $this->integration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $integration
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setIntegration(string $integration): self
|
||||||
|
{
|
||||||
|
$this->integration = $integration;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getJavascriptUrl(): string
|
||||||
|
{
|
||||||
|
return $this->javascriptUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $javascriptUrl
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setJavascriptUrl(string $javascriptUrl): self
|
||||||
|
{
|
||||||
|
$this->javascriptUrl = $javascriptUrl;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDeviceJavascriptUrl(): string
|
||||||
|
{
|
||||||
|
return $this->deviceJavascriptUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $deviceJavascriptUrl
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setDeviceJavascriptUrl(string $deviceJavascriptUrl): self
|
||||||
|
{
|
||||||
|
$this->deviceJavascriptUrl = $deviceJavascriptUrl;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getTransactionPossiblePaymentMethods(): array
|
||||||
|
{
|
||||||
|
return $this->transactionPossiblePaymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $transactionPossiblePaymentMethods
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setTransactionPossiblePaymentMethods(array $transactionPossiblePaymentMethods): self
|
||||||
|
{
|
||||||
|
$this->transactionPossiblePaymentMethods = $transactionPossiblePaymentMethods;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getTransactionId(): int
|
||||||
|
{
|
||||||
|
return $this->transactionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $transactionId
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setTransactionId(int $transactionId): self
|
||||||
|
{
|
||||||
|
$this->transactionId = $transactionId;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getSpaceId(): int
|
||||||
|
{
|
||||||
|
return $this->spaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $spaceId
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setSpaceId(int $spaceId): self
|
||||||
|
{
|
||||||
|
$this->spaceId = $spaceId;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getCartRecreateUrl(): ?string
|
||||||
|
{
|
||||||
|
return $this->cartRecreateUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $cartRecreateUrl
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setCartRecreateUrl(?string $cartRecreateUrl): self
|
||||||
|
{
|
||||||
|
$this->cartRecreateUrl = $cartRecreateUrl;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getCheckoutUrl(): ?string
|
||||||
|
{
|
||||||
|
return $this->checkoutUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $checkoutUrl
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setCheckoutUrl(?string $checkoutUrl): self
|
||||||
|
{
|
||||||
|
$this->checkoutUrl = $checkoutUrl;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace VRPaymentPayment\Core\Storefront\Account\Controller;
|
namespace VRPaymentPayment\Core\Storefront\Account\Controller;
|
||||||
|
|
||||||
@@ -20,51 +22,57 @@ use Symfony\Component\{
|
|||||||
};
|
};
|
||||||
use VRPaymentPayment\Core\{
|
use VRPaymentPayment\Core\{
|
||||||
Api\Transaction\Service\TransactionService,
|
Api\Transaction\Service\TransactionService,
|
||||||
Settings\Service\SettingsService
|
Checkout\Service\InvoiceService
|
||||||
};
|
};
|
||||||
|
|
||||||
#[Package('storefront')]
|
#[Package('storefront')]
|
||||||
#[Route(defaults: ['_routeScope' => ['storefront']])]
|
#[Route(defaults: ['_routeScope' => ['storefront']])]
|
||||||
|
/**
|
||||||
|
* This controller handles account-related actions for orders, specifically
|
||||||
|
* allowing customers to download their invoice documents.
|
||||||
|
*/
|
||||||
class AccountOrderController extends StorefrontController
|
class AccountOrderController extends StorefrontController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \VRPaymentPayment\Core\Settings\Service\SettingsService
|
* @var LoggerInterface
|
||||||
*/
|
*/
|
||||||
protected $settingsService;
|
protected LoggerInterface $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Psr\Log\LoggerInterface
|
* @var TransactionService
|
||||||
|
* Local transaction service for order data retrieval.
|
||||||
*/
|
*/
|
||||||
protected $logger;
|
protected TransactionService $transactionService;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService
|
|
||||||
*/
|
|
||||||
protected $transactionService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var RequestStack
|
* @var RequestStack
|
||||||
|
* Symfony service to access the current request context.
|
||||||
*/
|
*/
|
||||||
protected $requestStack;
|
protected RequestStack $requestStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AccountOrderController constructor.
|
* @var InvoiceService
|
||||||
* @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService
|
* Service to fetch invoice documents from WhitelabelMachineName.
|
||||||
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
|
|
||||||
* @param RequestStack $requestStack
|
|
||||||
*/
|
*/
|
||||||
public function __construct(SettingsService $settingsService, TransactionService $transactionService, RequestStack $requestStack)
|
private InvoiceService $invoiceService;
|
||||||
{
|
|
||||||
$this->settingsService = $settingsService;
|
/**
|
||||||
|
* @param TransactionService $transactionService
|
||||||
|
* @param RequestStack $requestStack
|
||||||
|
* @param InvoiceService $invoiceService
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
TransactionService $transactionService,
|
||||||
|
RequestStack $requestStack,
|
||||||
|
InvoiceService $invoiceService
|
||||||
|
) {
|
||||||
$this->transactionService = $transactionService;
|
$this->transactionService = $transactionService;
|
||||||
$this->requestStack = $requestStack;
|
$this->requestStack = $requestStack;
|
||||||
|
$this->invoiceService = $invoiceService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Psr\Log\LoggerInterface $logger
|
* @param LoggerInterface $logger
|
||||||
* @internal
|
|
||||||
* @required
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public function setLogger(LoggerInterface $logger): void
|
public function setLogger(LoggerInterface $logger): void
|
||||||
{
|
{
|
||||||
@@ -72,44 +80,60 @@ class AccountOrderController extends StorefrontController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download invoice document
|
* Downloads an invoice document for a specific order.
|
||||||
*
|
*
|
||||||
* @param string $orderId
|
* @param string $orderId The ID of the order.
|
||||||
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return Response The PDF document as a download response.
|
||||||
*
|
|
||||||
* @throws \VRPayment\Sdk\ApiException
|
|
||||||
* @throws \VRPayment\Sdk\Http\ConnectionException
|
|
||||||
* @throws \VRPayment\Sdk\VersioningException
|
|
||||||
*/
|
*/
|
||||||
#[Route("/vrpayment/account/order/download/invoice/document/{orderId}",
|
#[Route(
|
||||||
|
path: "/vrpayment/account/order/download/invoice/document/{orderId}",
|
||||||
name: "frontend.vrpayment.account.order.download.invoice.document",
|
name: "frontend.vrpayment.account.order.download.invoice.document",
|
||||||
methods: ['GET'])]
|
methods: ['GET']
|
||||||
|
)]
|
||||||
public function downloadInvoiceDocument(string $orderId, SalesChannelContext $salesChannelContext): Response
|
public function downloadInvoiceDocument(string $orderId, SalesChannelContext $salesChannelContext): Response
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
// Ensure the user is logged in.
|
||||||
$customer = $this->getLoggedInCustomer();
|
$customer = $this->getLoggedInCustomer();
|
||||||
$settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId());
|
|
||||||
|
// Fetch the transaction entity to verify ownership.
|
||||||
$transactionEntity = $this->transactionService->getByOrderId($orderId, $salesChannelContext->getContext());
|
$transactionEntity = $this->transactionService->getByOrderId($orderId, $salesChannelContext->getContext());
|
||||||
if (strcasecmp($customer->getCustomerNumber(), $transactionEntity->getData()['customerId']) != 0) {
|
|
||||||
|
// Security check: ensure the document belongs to the logged-in customer.
|
||||||
|
if (strcasecmp((string)$customer->getCustomerNumber(), (string)$transactionEntity->getData()['customerId']) != 0) {
|
||||||
throw new AccessDeniedException();
|
throw new AccessDeniedException();
|
||||||
}
|
}
|
||||||
$invoiceDocument = $settings->getApiClient()->getTransactionService()->getInvoiceDocument($settings->getSpaceId(), $transactionEntity->getTransactionId());
|
|
||||||
$forceDownload = true;
|
// Retrieve the invoice document metadata and content.
|
||||||
$filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '_', $invoiceDocument->getTitle()) . '.pdf';
|
/** @var object $invoiceDocument */
|
||||||
|
$invoiceDocument = $this->invoiceService->getInvoiceDocument($orderId, $salesChannelContext);
|
||||||
|
|
||||||
|
// Sanitize the filename for the download.
|
||||||
|
$filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '_', (string)$invoiceDocument->getTitle()) . '.pdf';
|
||||||
$disposition = HeaderUtils::makeDisposition(
|
$disposition = HeaderUtils::makeDisposition(
|
||||||
$forceDownload ? HeaderUtils::DISPOSITION_ATTACHMENT : HeaderUtils::DISPOSITION_INLINE,
|
HeaderUtils::DISPOSITION_ATTACHMENT,
|
||||||
$filename,
|
$filename,
|
||||||
$filename
|
$filename
|
||||||
);
|
);
|
||||||
$response = new Response(base64_decode($invoiceDocument->getData()));
|
|
||||||
$response->headers->set('Content-Type', $invoiceDocument->getMimeType());
|
// Create the response with the PDF content (base64 decoded).
|
||||||
|
$response = new Response(base64_decode((string)$invoiceDocument->getData()));
|
||||||
|
$response->headers->set('Content-Type', (string)$invoiceDocument->getMimeType());
|
||||||
$response->headers->set('Content-Disposition', $disposition);
|
$response->headers->set('Content-Disposition', $disposition);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
return $this->redirectToRoute('frontend.home.page');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Helper to retrieve the currently logged-in customer.
|
||||||
|
*
|
||||||
* @return CustomerEntity
|
* @return CustomerEntity
|
||||||
|
* @throws CustomerNotLoggedInException
|
||||||
*/
|
*/
|
||||||
protected function getLoggedInCustomer(): CustomerEntity
|
protected function getLoggedInCustomer(): CustomerEntity
|
||||||
{
|
{
|
||||||
@@ -119,6 +143,7 @@ class AccountOrderController extends StorefrontController
|
|||||||
throw new CustomerNotLoggedInException();
|
throw new CustomerNotLoggedInException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var SalesChannelContext|null $context */
|
||||||
$context = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
|
$context = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
|
||||||
|
|
||||||
if ($context && $context->getCustomer() && $context->getCustomer()->getGuest() === false) {
|
if ($context && $context->getCustomer() && $context->getCustomer()->getGuest() === false) {
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace VRPaymentPayment\Core\Storefront\Checkout\Controller;
|
namespace VRPaymentPayment\Core\Storefront\Checkout\Controller;
|
||||||
|
|
||||||
|
use VRPayment\Sdk\Model\TransactionState as SdkTransactionState;
|
||||||
|
use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shopware\Core\{
|
use Shopware\Core\{
|
||||||
Checkout\Cart\Cart,
|
|
||||||
Checkout\Cart\CartException,
|
Checkout\Cart\CartException,
|
||||||
Checkout\Cart\LineItemFactoryRegistry,
|
|
||||||
Checkout\Cart\SalesChannel\CartService,
|
|
||||||
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection,
|
|
||||||
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity,
|
|
||||||
Checkout\Order\OrderEntity,
|
|
||||||
Checkout\Order\SalesChannel\AbstractOrderRoute,
|
Checkout\Order\SalesChannel\AbstractOrderRoute,
|
||||||
Framework\Context,
|
|
||||||
Framework\DataAbstractionLayer\Search\Criteria,
|
|
||||||
Framework\DataAbstractionLayer\Search\Filter\EqualsFilter,
|
|
||||||
Framework\DataAbstractionLayer\Search\Sorting\FieldSorting,
|
|
||||||
Framework\Log\Package,
|
Framework\Log\Package,
|
||||||
Framework\Routing\Exception\MissingRequestParameterException,
|
Framework\Routing\RoutingException,
|
||||||
Framework\Uuid\Uuid,
|
Framework\DataAbstractionLayer\Search\Criteria,
|
||||||
Framework\Uuid\Exception\InvalidUuidException,
|
|
||||||
Framework\Validation\DataBag\RequestDataBag,
|
|
||||||
System\SalesChannel\SalesChannelContext
|
System\SalesChannel\SalesChannelContext
|
||||||
};
|
};
|
||||||
use Shopware\Storefront\{
|
use Shopware\Storefront\{
|
||||||
@@ -31,105 +23,90 @@ use Shopware\Storefront\{
|
|||||||
use Symfony\Component\{
|
use Symfony\Component\{
|
||||||
HttpFoundation\Request,
|
HttpFoundation\Request,
|
||||||
HttpFoundation\Response,
|
HttpFoundation\Response,
|
||||||
Routing\Attribute\Route,
|
Routing\Attribute\Route
|
||||||
Routing\Generator\UrlGeneratorInterface
|
|
||||||
};
|
|
||||||
use VRPayment\Sdk\{
|
|
||||||
Model\Transaction,
|
|
||||||
Model\TransactionState
|
|
||||||
};
|
};
|
||||||
use VRPaymentPayment\Core\{
|
use VRPaymentPayment\Core\{
|
||||||
Api\Transaction\Service\TransactionService,
|
Checkout\Service\CartRecoveryService,
|
||||||
Settings\Options\Integration,
|
Checkout\Service\PaymentIntegrationService,
|
||||||
Settings\Service\SettingsService,
|
Settings\Service\SettingsService
|
||||||
Storefront\Checkout\Struct\CheckoutPageData,
|
|
||||||
Util\Payload\CustomProducts\CustomProductsLineItemTypes
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class CheckoutController
|
|
||||||
*
|
|
||||||
* @package VRPaymentPayment\Core\Storefront\Checkout\Controller
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#[Package('checkout')]
|
#[Package('checkout')]
|
||||||
#[Route(defaults: ['_routeScope' => ['storefront']])]
|
#[Route(defaults: ['_routeScope' => ['storefront']])]
|
||||||
class CheckoutController extends StorefrontController {
|
/**
|
||||||
|
* This controller handles Storefront-specific actions for the WhitelabelMachineName integration,
|
||||||
/**
|
* such as rendering the payment page and recreating a cart from a failed order.
|
||||||
* @var \Shopware\Storefront\Page\GenericPageLoader
|
|
||||||
*/
|
*/
|
||||||
protected $genericLoader;
|
class CheckoutController extends StorefrontController
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* @var \Shopware\Core\Checkout\Cart\SalesChannel\CartService
|
* @var GenericPageLoaderInterface
|
||||||
|
* Loader for basic Shopware page data.
|
||||||
*/
|
*/
|
||||||
protected $cartService;
|
protected GenericPageLoaderInterface $genericLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \VRPaymentPayment\Core\Settings\Service\SettingsService
|
* @var SettingsService
|
||||||
|
* Plugin settings service.
|
||||||
*/
|
*/
|
||||||
protected $settingsService;
|
protected SettingsService $settingsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \VRPaymentPayment\Core\Settings\Struct\Settings
|
* @var LoggerInterface|null
|
||||||
|
* Logger for recording errors and important information.
|
||||||
*/
|
*/
|
||||||
protected $settings;
|
private ?LoggerInterface $logger = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService
|
* @var AbstractOrderRoute
|
||||||
|
* Shopware service for order retrieval.
|
||||||
*/
|
*/
|
||||||
protected $transactionService;
|
private AbstractOrderRoute $orderRoute;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Psr\Log\LoggerInterface
|
* @var CartRecoveryService
|
||||||
|
* Service to help customers recover their cart from a past order.
|
||||||
*/
|
*/
|
||||||
private $logger;
|
private CartRecoveryService $cartRecoveryService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Shopware\Core\Checkout\Cart\LineItemFactoryRegistry
|
* @var PaymentIntegrationService
|
||||||
|
* Service to provide the integration parameters (JS URL, transaction ID, etc.).
|
||||||
*/
|
*/
|
||||||
private $lineItemFactoryRegistry;
|
private PaymentIntegrationService $paymentIntegrationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Shopware\Core\Checkout\Order\SalesChannel\AbstractOrderRoute
|
* @var TransactionService
|
||||||
|
* Service to check transaction details.
|
||||||
*/
|
*/
|
||||||
private $orderRoute;
|
private TransactionService $transactionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PaymentController constructor.
|
* @param SettingsService $settingsService
|
||||||
*
|
* @param GenericPageLoaderInterface $genericLoader
|
||||||
* @param \Shopware\Core\Checkout\Cart\LineItemFactoryRegistry $lineItemFactoryRegistry
|
* @param AbstractOrderRoute $orderRoute
|
||||||
* @param \Shopware\Core\Checkout\Cart\SalesChannel\CartService $cartService
|
* @param CartRecoveryService $cartRecoveryService
|
||||||
* @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService
|
* @param PaymentIntegrationService $paymentIntegrationService
|
||||||
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
|
|
||||||
* @param \Shopware\Storefront\Page\GenericPageLoaderInterface $genericLoader
|
|
||||||
* @param \Shopware\Core\Checkout\Order\SalesChannel\AbstractOrderRoute $orderRoute
|
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
LineItemFactoryRegistry $lineItemFactoryRegistry,
|
|
||||||
CartService $cartService,
|
|
||||||
SettingsService $settingsService,
|
SettingsService $settingsService,
|
||||||
TransactionService $transactionService,
|
|
||||||
GenericPageLoaderInterface $genericLoader,
|
GenericPageLoaderInterface $genericLoader,
|
||||||
AbstractOrderRoute $orderRoute
|
AbstractOrderRoute $orderRoute,
|
||||||
)
|
CartRecoveryService $cartRecoveryService,
|
||||||
{
|
PaymentIntegrationService $paymentIntegrationService,
|
||||||
$this->cartService = $cartService;
|
TransactionService $transactionService
|
||||||
|
) {
|
||||||
$this->genericLoader = $genericLoader;
|
$this->genericLoader = $genericLoader;
|
||||||
$this->settingsService = $settingsService;
|
$this->settingsService = $settingsService;
|
||||||
$this->transactionService = $transactionService;
|
|
||||||
$this->lineItemFactoryRegistry = $lineItemFactoryRegistry;
|
|
||||||
$this->orderRoute = $orderRoute;
|
$this->orderRoute = $orderRoute;
|
||||||
|
$this->cartRecoveryService = $cartRecoveryService;
|
||||||
|
$this->paymentIntegrationService = $paymentIntegrationService;
|
||||||
|
$this->transactionService = $transactionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Psr\Log\LoggerInterface $logger
|
* @param LoggerInterface $logger
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @required
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public function setLogger(LoggerInterface $logger): void
|
public function setLogger(LoggerInterface $logger): void
|
||||||
{
|
{
|
||||||
@@ -137,14 +114,11 @@ class CheckoutController extends StorefrontController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
|
* Renders the WhitelabelMachineName payment page (usually contains the iframe or lightbox script).
|
||||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
|
||||||
*
|
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
|
||||||
* @throws \VRPayment\Sdk\ApiException
|
|
||||||
* @throws \VRPayment\Sdk\Http\ConnectionException
|
|
||||||
* @throws \VRPayment\Sdk\VersioningException
|
|
||||||
*
|
*
|
||||||
|
* @param SalesChannelContext $salesChannelContext The current context.
|
||||||
|
* @param Request $request The incoming request.
|
||||||
|
* @return Response The rendered payment page.
|
||||||
*/
|
*/
|
||||||
#[Route(
|
#[Route(
|
||||||
path: "/vrpayment/checkout/pay",
|
path: "/vrpayment/checkout/pay",
|
||||||
@@ -154,191 +128,57 @@ class CheckoutController extends StorefrontController {
|
|||||||
)]
|
)]
|
||||||
public function pay(SalesChannelContext $salesChannelContext, Request $request): Response
|
public function pay(SalesChannelContext $salesChannelContext, Request $request): Response
|
||||||
{
|
{
|
||||||
$orderId = $request->query->get('orderId');
|
$orderId = (string)$request->query->get('orderId');
|
||||||
|
|
||||||
if (empty($orderId)) {
|
if (empty($orderId)) {
|
||||||
throw new MissingRequestParameterException('orderId');
|
throw RoutingException::missingRequestParameter('orderId');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration
|
try {
|
||||||
$this->settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId());
|
// Load the order with necessary associations for the product table and addresses.
|
||||||
|
$criteria = new Criteria([$orderId]);
|
||||||
|
$criteria->addAssociation('lineItems.product')
|
||||||
|
->addAssociation('deliveries.shippingOrderAddress.country')
|
||||||
|
->addAssociation('orderCustomer.customer')
|
||||||
|
->addAssociation('transactions.paymentMethod');
|
||||||
|
|
||||||
$transaction = $this->getTransaction($orderId, $salesChannelContext->getContext());
|
$order = $this->orderRoute->load(new Request(), $salesChannelContext, $criteria)->getOrders()->first();
|
||||||
$recreateCartUrl = $this->generateUrl(
|
|
||||||
'frontend.vrpayment.checkout.recreate-cart',
|
|
||||||
['orderId' => $orderId,],
|
|
||||||
UrlGeneratorInterface::ABSOLUTE_URL
|
|
||||||
);
|
|
||||||
|
|
||||||
if (in_array(
|
if (!$order) {
|
||||||
$transaction->getState(),
|
throw RoutingException::missingRequestParameter('orderId');
|
||||||
[
|
|
||||||
TransactionState::AUTHORIZED,
|
|
||||||
TransactionState::COMPLETED,
|
|
||||||
TransactionState::FULFILL,
|
|
||||||
]
|
|
||||||
)) {
|
|
||||||
return $this->redirect($transaction->getSuccessUrl(), Response::HTTP_MOVED_PERMANENTLY);
|
|
||||||
} else {
|
|
||||||
if (in_array(
|
|
||||||
$transaction->getState(),
|
|
||||||
[
|
|
||||||
TransactionState::DECLINE,
|
|
||||||
TransactionState::FAILED,
|
|
||||||
TransactionState::VOIDED,
|
|
||||||
]
|
|
||||||
)) {
|
|
||||||
return $this->redirect($transaction->getFailedUrl(), Response::HTTP_MOVED_PERMANENTLY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$possiblePaymentMethods = $this->settings->getApiClient()
|
// Fetch the configuration required for the frontend integration.
|
||||||
->getTransactionService()
|
$paymentConfig = $this->paymentIntegrationService->getPaymentConfig($orderId, $salesChannelContext);
|
||||||
->fetchPaymentMethods(
|
|
||||||
$this->settings->getSpaceId(),
|
|
||||||
$transaction->getId(),
|
|
||||||
$this->settings->getIntegration()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (empty($possiblePaymentMethods)) {
|
// Load a generic Shopware page to have layout headers/footers.
|
||||||
$this->addFlash('danger', $this->trans('vrpayment.paymentMethod.notAvailable'));
|
$page = $this->genericLoader->load($request, $salesChannelContext);
|
||||||
return $this->redirect($recreateCartUrl, Response::HTTP_MOVED_PERMANENTLY);
|
$page->addExtension('vRPaymentData', $paymentConfig);
|
||||||
}
|
|
||||||
|
|
||||||
$javascriptUrl = $this->getTransactionJavaScriptUrl($transaction->getId());
|
// Assign the order to the page so the templates can access page.order.
|
||||||
|
$page->assign(['order' => $order]);
|
||||||
// Set Checkout Page Data
|
|
||||||
$checkoutPageData = (new CheckoutPageData())
|
|
||||||
->setIntegration($this->settings->getIntegration())
|
|
||||||
->setJavascriptUrl($javascriptUrl)
|
|
||||||
->setDeviceJavascriptUrl($this->settings->getSpaceId(), Uuid::randomHex())
|
|
||||||
->setTransactionPossiblePaymentMethods($possiblePaymentMethods)
|
|
||||||
->setCheckoutUrl($this->generateUrl(
|
|
||||||
'frontend.vrpayment.checkout.pay',
|
|
||||||
['orderId' => $orderId,],
|
|
||||||
UrlGeneratorInterface::ABSOLUTE_URL
|
|
||||||
))
|
|
||||||
->setCartRecreateUrl($recreateCartUrl);
|
|
||||||
$page = $this->load($request, $salesChannelContext);
|
|
||||||
$page->addExtension('vRPaymentData', $checkoutPageData);
|
|
||||||
|
|
||||||
|
// Render the specialized Twig template for WhitelabelMachineName.
|
||||||
return $this->renderStorefront(
|
return $this->renderStorefront(
|
||||||
'@VRPaymentPayment/storefront/page/checkout/order/vrpayment.html.twig',
|
'@VRPaymentPayment/storefront/page/checkout/order/vrpayment.html.twig',
|
||||||
['page' => $page]
|
['page' => $page]
|
||||||
);
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if ($this->logger) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
$this->addFlash('danger', $this->trans('vrpayment.paymentMethod.notAvailable'));
|
||||||
|
return $this->redirectToRoute('frontend.home.page');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get transaction Javascript URL
|
* Redirects the user to a route that recreates their cart from an existing order.
|
||||||
*
|
* This is useful for allowing users to try payment again with different details.
|
||||||
* @param int $transactionId
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \VRPayment\Sdk\ApiException
|
|
||||||
* @throws \VRPayment\Sdk\Http\ConnectionException
|
|
||||||
* @throws \VRPayment\Sdk\VersioningException
|
|
||||||
*/
|
|
||||||
private function getTransactionJavaScriptUrl(int $transactionId): string
|
|
||||||
{
|
|
||||||
$javascriptUrl = '';
|
|
||||||
switch ($this->settings->getIntegration()) {
|
|
||||||
case Integration::IFRAME:
|
|
||||||
$javascriptUrl = $this->settings->getApiClient()->getTransactionIframeService()
|
|
||||||
->javascriptUrl($this->settings->getSpaceId(), $transactionId);
|
|
||||||
break;
|
|
||||||
case Integration::LIGHTBOX:
|
|
||||||
$javascriptUrl = $this->settings->getApiClient()->getTransactionLightboxService()
|
|
||||||
->javascriptUrl($this->settings->getSpaceId(), $transactionId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$this->logger->critical(strtr('invalid integration : :integration', [':integration' => $this->settings->getIntegration()]));
|
|
||||||
|
|
||||||
}
|
|
||||||
return $javascriptUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $orderId
|
|
||||||
* @param \Shopware\Core\Framework\Context $context
|
|
||||||
*
|
|
||||||
* @return \VRPayment\Sdk\Model\Transaction
|
|
||||||
* @throws \VRPayment\Sdk\ApiException
|
|
||||||
* @throws \VRPayment\Sdk\Http\ConnectionException
|
|
||||||
* @throws \VRPayment\Sdk\VersioningException
|
|
||||||
*/
|
|
||||||
private function getTransaction($orderId, Context $context): Transaction
|
|
||||||
{
|
|
||||||
$transactionEntity = $this->transactionService->getByOrderId($orderId, $context);
|
|
||||||
return $this->settings->getApiClient()->getTransactionService()->read($this->settings->getSpaceId(), $transactionEntity->getTransactionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
|
||||||
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
|
|
||||||
*
|
|
||||||
* @return \Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPage
|
|
||||||
*/
|
|
||||||
protected function load(Request $request, SalesChannelContext $salesChannelContext): CheckoutFinishPage
|
|
||||||
{
|
|
||||||
$page = CheckoutFinishPage::createFrom($this->genericLoader->load($request, $salesChannelContext));
|
|
||||||
$page->setOrder($this->getOrder($request, $salesChannelContext));
|
|
||||||
|
|
||||||
return $page;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
|
||||||
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
|
|
||||||
*
|
|
||||||
* @return \Shopware\Core\Checkout\Order\OrderEntity
|
|
||||||
*/
|
|
||||||
private function getOrder(Request $request, SalesChannelContext $salesChannelContext): OrderEntity
|
|
||||||
{
|
|
||||||
|
|
||||||
$orderId = $request->get('orderId');
|
|
||||||
if (!$orderId) {
|
|
||||||
throw new MissingRequestParameterException('orderId', '/orderId');
|
|
||||||
}
|
|
||||||
|
|
||||||
$criteria = (new Criteria([$orderId]))
|
|
||||||
->addAssociation('lineItems.cover')
|
|
||||||
->addAssociation('transactions.paymentMethod')
|
|
||||||
->addAssociation('deliveries.shippingMethod');
|
|
||||||
|
|
||||||
$customer = $salesChannelContext->getCustomer();
|
|
||||||
if ($customer !== null) {
|
|
||||||
$criteria = $criteria->addFilter(new EqualsFilter('order.orderCustomer.customerId', $customer->getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$criteria->getAssociation('transactions')->addSorting(new FieldSorting('createdAt'));
|
|
||||||
|
|
||||||
try {
|
|
||||||
$searchResult = $this->orderRoute
|
|
||||||
->load(new Request(), $salesChannelContext, $criteria)
|
|
||||||
->getOrders();
|
|
||||||
} catch (InvalidUuidException $e) {
|
|
||||||
throw CartException::orderNotFound($orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var OrderEntity|null $order */
|
|
||||||
$order = $searchResult->get($orderId);
|
|
||||||
|
|
||||||
if (!$order) {
|
|
||||||
throw CartException::orderNotFound($orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recreate Cart
|
|
||||||
*
|
|
||||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
|
||||||
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
|
|
||||||
*
|
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
|
||||||
*
|
*
|
||||||
|
* @param Request $request The incoming request.
|
||||||
|
* @param SalesChannelContext $salesChannelContext The context.
|
||||||
|
* @return Response Redirect to the checkout confirmation page.
|
||||||
*/
|
*/
|
||||||
#[Route(
|
#[Route(
|
||||||
path: "/vrpayment/checkout/recreate-cart",
|
path: "/vrpayment/checkout/recreate-cart",
|
||||||
@@ -346,223 +186,49 @@ class CheckoutController extends StorefrontController {
|
|||||||
options: ["seo" => false],
|
options: ["seo" => false],
|
||||||
methods: ["GET"],
|
methods: ["GET"],
|
||||||
)]
|
)]
|
||||||
public function recreateCart(Request $request, SalesChannelContext $salesChannelContext)
|
public function recreateCart(Request $request, SalesChannelContext $salesChannelContext): Response
|
||||||
{
|
{
|
||||||
$orderId = $request->query->get('orderId');
|
$orderId = (string)$request->query->get('orderId');
|
||||||
|
|
||||||
if (empty($orderId)) {
|
if (empty($orderId)) {
|
||||||
throw new MissingRequestParameterException('orderId');
|
throw RoutingException::missingRequestParameter('orderId');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adoption for Headless Storefronts
|
|
||||||
$orderRepo = $this->container->get('order.repository');
|
|
||||||
$criteria = new Criteria([$orderId]);
|
|
||||||
|
|
||||||
$orderEntity = $orderRepo->search($criteria, $salesChannelContext->getContext())->first();
|
|
||||||
|
|
||||||
if($orderEntity->getSalesChannelId() !== $salesChannelContext->getSalesChannelId()) {
|
|
||||||
$this->settings = $this->settingsService->getSettings($orderEntity->getSalesChannelId());
|
|
||||||
$trans = $this->getTransaction($orderId, $salesChannelContext->getContext());
|
|
||||||
return $this->redirect($trans->getSuccessUrl());
|
|
||||||
}
|
|
||||||
// End Adoption for Headless Storefronts
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->cartService->deleteCart($salesChannelContext);
|
// Find the order that should be recovered.
|
||||||
$cart = $this->cartService->createNew($salesChannelContext->getToken());
|
$order = $this->cartRecoveryService->getOrderEntity($orderId, $salesChannelContext->getContext());
|
||||||
|
|
||||||
// Configuration
|
// Security: Order must belong to the active sales channel.
|
||||||
$this->settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId());
|
if ($order->getSalesChannelId() !== $salesChannelContext->getSalesChannelId()) {
|
||||||
$orderEntity = $this->getOrder($request, $salesChannelContext);
|
|
||||||
$lastTransaction = $orderEntity->getTransactions()->last();
|
|
||||||
if ($lastTransaction && !$lastTransaction->getPaymentMethod()->getAfterOrderEnabled()) {
|
|
||||||
return $this->redirectToRoute('frontend.home.page');
|
return $this->redirectToRoute('frontend.home.page');
|
||||||
}
|
}
|
||||||
|
|
||||||
$transaction = $this->getTransaction($orderId, $salesChannelContext->getContext());
|
// Perform the recovery process.
|
||||||
if (!empty($transaction->getUserFailureMessage())) {
|
$transactionEntity = $this->transactionService->getByOrderId($order->getId(), $salesChannelContext->getContext());
|
||||||
|
if ($transactionEntity) {
|
||||||
|
$transaction = $this->transactionService->read($transactionEntity->getTransactionId(), $salesChannelContext->getSalesChannelId());
|
||||||
|
if (in_array($transaction->getState(), [
|
||||||
|
SdkTransactionState::AUTHORIZED,
|
||||||
|
SdkTransactionState::CONFIRMED,
|
||||||
|
SdkTransactionState::FULFILL
|
||||||
|
])) {
|
||||||
|
return $this->redirectToRoute('frontend.checkout.finish.page', ['orderId' => $orderId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($transaction->getUserFailureMessage()) {
|
||||||
$this->addFlash('danger', $transaction->getUserFailureMessage());
|
$this->addFlash('danger', $transaction->getUserFailureMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
$orderItems = $orderEntity->getLineItems();
|
|
||||||
$hasCustomProducts = $this->hasCustomProducts($orderItems);
|
|
||||||
|
|
||||||
if ($hasCustomProducts === true) {
|
|
||||||
$cart = $this->addCustomProducts($orderItems, $request, $salesChannelContext);
|
|
||||||
}
|
}
|
||||||
|
$this->cartRecoveryService->recreateCartFromOrder($order, $salesChannelContext);
|
||||||
foreach ($orderItems as $orderLineItemEntity) {
|
|
||||||
$type = $orderLineItemEntity->getType();
|
|
||||||
|
|
||||||
if ($type !== CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT || $orderLineItemEntity->getParentid() !== null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$lineItem = $this->lineItemFactoryRegistry->create([
|
|
||||||
'id' => $orderLineItemEntity->getId(),
|
|
||||||
'quantity' => $orderLineItemEntity->getQuantity(),
|
|
||||||
'referencedId' => $orderLineItemEntity->getReferencedId(),
|
|
||||||
'type' => $type,
|
|
||||||
], $salesChannelContext);
|
|
||||||
|
|
||||||
$lineItemPayload = $orderLineItemEntity->getPayload();
|
|
||||||
if (!empty($lineItemPayload)) {
|
|
||||||
$lineItem->setPayload($lineItemPayload);
|
|
||||||
}
|
|
||||||
|
|
||||||
$cart = $this->cartService->add($cart, $lineItem, $salesChannelContext);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$this->addFlash('danger', $this->trans('error.addToCartError'));
|
$this->addFlash('danger', $this->trans('error.addToCartError'));
|
||||||
|
if ($this->logger) {
|
||||||
$this->logger->critical($exception->getMessage());
|
$this->logger->critical($exception->getMessage());
|
||||||
|
}
|
||||||
return $this->redirectToRoute('frontend.home.page');
|
return $this->redirectToRoute('frontend.home.page');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the user back to the checkout confirm page with their items restored.
|
||||||
return $this->redirectToRoute('frontend.checkout.confirm.page');
|
return $this->redirectToRoute('frontend.checkout.confirm.page');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param OrderLineItemCollection $orderItems
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function hasCustomProducts(OrderLineItemCollection $orderItems): bool
|
|
||||||
{
|
|
||||||
foreach ($orderItems as $orderItem) {
|
|
||||||
if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param OrderLineItemCollection $orderItems
|
|
||||||
* @param string $parentId
|
|
||||||
*
|
|
||||||
* @return OrderLineItemEntity|null
|
|
||||||
*/
|
|
||||||
private function getCustomProduct(OrderLineItemCollection $orderItems, string $parentId): ?OrderLineItemEntity
|
|
||||||
{
|
|
||||||
foreach ($orderItems as $orderItem) {
|
|
||||||
if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT && $orderItem->getParentId() === $parentId) {
|
|
||||||
return $orderItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param OrderLineItemCollection $orderItems
|
|
||||||
* @param string $parentId
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getCustomProductOptions(OrderLineItemCollection $orderItems, string $parentId): array
|
|
||||||
{
|
|
||||||
$options = [];
|
|
||||||
foreach ($orderItems as $orderItem) {
|
|
||||||
if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS_OPTION && $orderItem->getParentId() === $parentId) {
|
|
||||||
$options[] = $orderItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $orderItems
|
|
||||||
* @param $request
|
|
||||||
* @param $salesChannelContext
|
|
||||||
*
|
|
||||||
* @return Cart
|
|
||||||
*/
|
|
||||||
private function addCustomProducts(OrderLineItemCollection $orderItems, Request $request, SalesChannelContext $salesChannelContext): Cart
|
|
||||||
{
|
|
||||||
|
|
||||||
$cart = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext);
|
|
||||||
if (!\class_exists('Swag\\CustomizedProducts\\Core\\Checkout\\Cart\\Route\\AddCustomizedProductsToCartRoute')) {
|
|
||||||
return $cart;
|
|
||||||
}
|
|
||||||
|
|
||||||
$customProductsService = $this->get('Swag\CustomizedProducts\Core\Checkout\Cart\Route\AddCustomizedProductsToCartRoute');
|
|
||||||
|
|
||||||
foreach ($orderItems as $orderItem) {
|
|
||||||
if ($orderItem->getType() !== CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$product = $this->getCustomProduct($orderItems, $orderItem->getId());
|
|
||||||
$productOptions = $this->getCustomProductOptions($orderItems, $orderItem->getId());
|
|
||||||
$optionValues = $this->getOptionValues($productOptions);
|
|
||||||
|
|
||||||
$params = new RequestDataBag([
|
|
||||||
'customized-products-template' => new RequestDataBag([
|
|
||||||
'id' => $orderItem->getReferencedId(),
|
|
||||||
'options' => new RequestDataBag($optionValues),
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$request->request->add(
|
|
||||||
[
|
|
||||||
'lineItems' =>
|
|
||||||
[
|
|
||||||
$product->getProductId() =>
|
|
||||||
[
|
|
||||||
'quantity' => $orderItem->getQuantity(),
|
|
||||||
'id' => $product->getProductId(),
|
|
||||||
'type' => CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT,
|
|
||||||
'referencedId' => $product->getReferencedId(),
|
|
||||||
'stackable' => $orderItem->getStackable(),
|
|
||||||
'removable' => $orderItem->getRemovable(),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$customProductsService->add($params, $request, $salesChannelContext, $cart);
|
|
||||||
$cart = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $productOptions
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getOptionValues(array $productOptions): array
|
|
||||||
{
|
|
||||||
$optionValues = [];
|
|
||||||
foreach ($productOptions as $productOption) {
|
|
||||||
$optionType = $productOption->getPayload()['type'] ?: '';
|
|
||||||
|
|
||||||
switch ($optionType) {
|
|
||||||
case CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_IMAGE_UPLOAD:
|
|
||||||
case CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_FILE_UPLOAD:
|
|
||||||
$media = $productOption->getPayload()['media'] ?: [];
|
|
||||||
foreach ($media as $mediaItem) {
|
|
||||||
$optionValues[$productOption->getReferencedId()] = new RequestDataBag([
|
|
||||||
'media' => new RequestDataBag([
|
|
||||||
$mediaItem['filename'] => new RequestDataBag([
|
|
||||||
'id' => $mediaItem['mediaId'],
|
|
||||||
'filename' => $mediaItem['filename'],
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$optionValues[$productOption->getReferencedId()] = new RequestDataBag([
|
|
||||||
'value' => $productOption->getPayload()['value'] ?: '',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $optionValues;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +1,70 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace VRPaymentPayment\Core\Storefront\Checkout\Subscriber;
|
namespace VRPaymentPayment\Core\Storefront\Checkout\Subscriber;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shopware\Core\{Checkout\Order\Aggregate\OrderTransaction\OrderTransactionCollection,
|
use Shopware\Core\{
|
||||||
|
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionCollection,
|
||||||
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates,
|
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates,
|
||||||
Checkout\Order\OrderEntity,
|
Checkout\Order\OrderEntity,
|
||||||
Content\MailTemplate\Service\Event\MailBeforeValidateEvent};
|
Content\MailTemplate\Service\Event\MailBeforeValidateEvent
|
||||||
|
};
|
||||||
use Shopware\Core\Checkout\Payment\PaymentMethodCollection;
|
use Shopware\Core\Checkout\Payment\PaymentMethodCollection;
|
||||||
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
|
|
||||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
|
|
||||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
|
|
||||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
|
|
||||||
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||||
use Shopware\Storefront\Page\Account\Order\AccountEditOrderPageLoadedEvent;
|
use Shopware\Storefront\Page\Account\Order\AccountEditOrderPageLoadedEvent;
|
||||||
use Shopware\Storefront\Page\Account\PaymentMethod\AccountPaymentMethodPageLoadedEvent;
|
use Shopware\Storefront\Page\Account\PaymentMethod\AccountPaymentMethodPageLoadedEvent;
|
||||||
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
|
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
|
||||||
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedEvent;
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use VRPaymentPayment\Core\{Api\Transaction\Service\TransactionService,
|
use VRPaymentPayment\Core\Checkout\PaymentHandler\VRPaymentPaymentHandler;
|
||||||
Checkout\PaymentHandler\VRPaymentPaymentHandler,
|
use VRPaymentPayment\Core\Settings\Service\SettingsService;
|
||||||
Settings\Service\SettingsService,
|
use VRPaymentPayment\Core\Checkout\Service\PaymentMethodFilterService;
|
||||||
Settings\Struct\Settings,
|
use VRPaymentPayment\Core\Checkout\Service\PaymentIntegrationService;
|
||||||
Util\PaymentMethodUtil};
|
|
||||||
use VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService;
|
|
||||||
use VRPaymentPayment\Sdk\{Model\AddressCreate,
|
|
||||||
Model\ChargeAttempt,
|
|
||||||
Model\CreationEntityState,
|
|
||||||
Model\CriteriaOperator,
|
|
||||||
Model\EntityQuery,
|
|
||||||
Model\EntityQueryFilter,
|
|
||||||
Model\EntityQueryFilterType,
|
|
||||||
Model\LineItemAttributeCreate,
|
|
||||||
Model\LineItemCreate,
|
|
||||||
Model\LineItemType,
|
|
||||||
Model\TaxCreate,
|
|
||||||
Model\Transaction,
|
|
||||||
Model\TransactionCreate,
|
|
||||||
Model\TransactionPending};
|
|
||||||
use Shopware\Core\Framework\Struct\ArrayEntity;
|
|
||||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CheckoutSubscriber
|
* This subscriber listens to page load events in the Storefront to filter out
|
||||||
*
|
* WhitelabelMachineName payment methods that are not applicable for the current cart or customer.
|
||||||
* @package VRPaymentPayment\Storefront\Checkout\Subscriber
|
|
||||||
*/
|
*/
|
||||||
class CheckoutSubscriber implements EventSubscriberInterface
|
class CheckoutSubscriber implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected LoggerInterface $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Psr\Log\LoggerInterface
|
* @var SettingsService
|
||||||
*/
|
*/
|
||||||
protected $logger;
|
private SettingsService $settingsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService
|
* @var PaymentMethodFilterService
|
||||||
*/
|
*/
|
||||||
private $paymentMethodConfigurationService;
|
private PaymentMethodFilterService $paymentMethodFilterService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService
|
* @var PaymentIntegrationService
|
||||||
*/
|
*/
|
||||||
private $transactionService;
|
private PaymentIntegrationService $paymentIntegrationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \VRPaymentPayment\Core\Settings\Service\SettingsService
|
* @param SettingsService $settingsService
|
||||||
|
* @param PaymentMethodFilterService $paymentMethodFilterService
|
||||||
|
* @param PaymentIntegrationService $paymentIntegrationService
|
||||||
*/
|
*/
|
||||||
private $settingsService;
|
public function __construct(
|
||||||
|
SettingsService $settingsService,
|
||||||
/**
|
PaymentMethodFilterService $paymentMethodFilterService,
|
||||||
* @var \VRPaymentPayment\Core\Util\PaymentMethodUtil
|
PaymentIntegrationService $paymentIntegrationService
|
||||||
*/
|
) {
|
||||||
private $paymentMethodUtil;
|
|
||||||
|
|
||||||
/** @var EntityRepository */
|
|
||||||
private EntityRepository $paymentMethodRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CheckoutSubscriber constructor.
|
|
||||||
*
|
|
||||||
* @param \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService $paymentMethodConfigurationService
|
|
||||||
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
|
|
||||||
* @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService
|
|
||||||
* @param \VRPaymentPayment\Core\Util\PaymentMethodUtil $paymentMethodUtil
|
|
||||||
* @param EntityRepository $paymentMethodRepository
|
|
||||||
*/
|
|
||||||
public function __construct(PaymentMethodConfigurationService $paymentMethodConfigurationService, TransactionService $transactionService, SettingsService $settingsService, PaymentMethodUtil $paymentMethodUtil, EntityRepository $paymentMethodRepository)
|
|
||||||
{
|
|
||||||
$this->paymentMethodConfigurationService = $paymentMethodConfigurationService;
|
|
||||||
$this->transactionService = $transactionService;
|
|
||||||
$this->settingsService = $settingsService;
|
$this->settingsService = $settingsService;
|
||||||
$this->paymentMethodUtil = $paymentMethodUtil;
|
$this->paymentMethodFilterService = $paymentMethodFilterService;
|
||||||
$this->paymentMethodRepository = $paymentMethodRepository;
|
$this->paymentIntegrationService = $paymentIntegrationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Psr\Log\LoggerInterface $logger
|
* @param LoggerInterface $logger
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @required
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public function setLogger(LoggerInterface $logger): void
|
public function setLogger(LoggerInterface $logger): void
|
||||||
{
|
{
|
||||||
@@ -108,38 +72,75 @@ class CheckoutSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Register events to listen to.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function getSubscribedEvents(): array
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
CheckoutConfirmPageLoadedEvent::class => 'onCheckoutConfirmLoaded',
|
CheckoutConfirmPageLoadedEvent::class => 'onPageLoaded',
|
||||||
AccountEditOrderPageLoadedEvent::class => 'onAccountOrderEditLoaded',
|
AccountEditOrderPageLoadedEvent::class => 'onPageLoaded',
|
||||||
AccountPaymentMethodPageLoadedEvent::class => 'onAccountPaymentMethodLoaded',
|
AccountPaymentMethodPageLoadedEvent::class => 'onPageLoaded',
|
||||||
"subscription." . CheckoutConfirmPageLoadedEvent::class => ['onConfirmPageLoaded', 1],
|
"subscription." . CheckoutConfirmPageLoadedEvent::class => ['onPageLoaded', 1],
|
||||||
MailBeforeValidateEvent::class => ['onMailBeforeValidate', 1],
|
MailBeforeValidateEvent::class => ['onMailBeforeValidate', 1],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop order emails being sent out
|
* Handles filtering of payment methods when a relevant page is loaded.
|
||||||
*
|
*
|
||||||
* @param \Shopware\Core\Content\MailTemplate\Service\Event\MailBeforeValidateEvent $event
|
* @param mixed $event The page loaded event.
|
||||||
|
*/
|
||||||
|
public function onPageLoaded($event): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$salesChannelContext = $event->getSalesChannelContext();
|
||||||
|
|
||||||
|
// Access the payment methods available for the current page.
|
||||||
|
$paymentMethodCollection = $event->getPage()->getPaymentMethods();
|
||||||
|
|
||||||
|
// Delegate filtering to the centralized service.
|
||||||
|
$filteredCollection = $this->paymentMethodFilterService->filterPaymentMethods(
|
||||||
|
$paymentMethodCollection,
|
||||||
|
$salesChannelContext,
|
||||||
|
$event
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the page with the filtered list.
|
||||||
|
$event->getPage()->setPaymentMethods($filteredCollection);
|
||||||
|
|
||||||
|
// If we are on a checkout or account page and have a pending transaction, provide integration data.
|
||||||
|
$transactionId = $salesChannelContext->getContext()->getExtension('vrpayment_transaction_id');
|
||||||
|
if ($transactionId) {
|
||||||
|
$paymentConfig = $this->paymentIntegrationService->getConfigForTransaction(
|
||||||
|
(int) $transactionId->getVars()['value'],
|
||||||
|
$salesChannelContext
|
||||||
|
);
|
||||||
|
$event->getPage()->addExtension('vRPaymentData', $paymentConfig);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles logic before a mail is validated/sent.
|
||||||
|
*
|
||||||
|
* @param MailBeforeValidateEvent $event
|
||||||
*/
|
*/
|
||||||
public function onMailBeforeValidate(MailBeforeValidateEvent $event): void
|
public function onMailBeforeValidate(MailBeforeValidateEvent $event): void
|
||||||
{
|
{
|
||||||
$templateData = $event->getTemplateData();
|
$templateData = $event->getTemplateData();
|
||||||
|
|
||||||
/**
|
/** @var OrderEntity|null $order */
|
||||||
* @var $order \Shopware\Core\Checkout\Order\OrderEntity
|
|
||||||
*/
|
|
||||||
$order = !empty($templateData['order']) && $templateData['order'] instanceof OrderEntity ? $templateData['order'] : null;
|
$order = !empty($templateData['order']) && $templateData['order'] instanceof OrderEntity ? $templateData['order'] : null;
|
||||||
|
|
||||||
if (!empty($order) && $order->getAmountTotal() > 0) {
|
if (!empty($order) && $order->getAmountTotal() > 0) {
|
||||||
|
// Check if WhitelabelMachineName emails are enabled for this sales channel.
|
||||||
|
$isVRPaymentEmailSettingEnabled = (bool)$this->settingsService->getSettings($order->getSalesChannelId())->isEmailEnabled();
|
||||||
|
|
||||||
$isVRPaymentEmailSettingEnabled = $this->settingsService->getSettings($order->getSalesChannelId())->isEmailEnabled();
|
if (!$isVRPaymentEmailSettingEnabled) {
|
||||||
|
|
||||||
if (!$isVRPaymentEmailSettingEnabled) { //setting is disabled
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,254 +148,30 @@ class CheckoutSubscriber implements EventSubscriberInterface
|
|||||||
if (!($orderTransactions instanceof OrderTransactionCollection)) {
|
if (!($orderTransactions instanceof OrderTransactionCollection)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$orderTransactionLast = $orderTransactions->last();
|
$orderTransactionLast = $orderTransactions->last();
|
||||||
if (empty($orderTransactionLast) || empty($orderTransactionLast->getPaymentMethod())) { // no payment method available
|
if (empty($orderTransactionLast) || empty($orderTransactionLast->getPaymentMethod())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the payment method used belongs to this plugin.
|
||||||
$isVRPaymentPM = VRPaymentPaymentHandler::class == $orderTransactionLast->getPaymentMethod()->getHandlerIdentifier();
|
$isVRPaymentPM = VRPaymentPaymentHandler::class == $orderTransactionLast->getPaymentMethod()->getHandlerIdentifier();
|
||||||
if (!$isVRPaymentPM) { // not our payment method
|
if (!$isVRPaymentPM) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if the transaction is in a state where an email should be handled.
|
||||||
$isOrderTransactionStateOpen = in_array(
|
$isOrderTransactionStateOpen = in_array(
|
||||||
$orderTransactionLast->getStateMachineState()->getTechnicalName(), [
|
$orderTransactionLast->getStateMachineState()->getTechnicalName(),
|
||||||
|
[
|
||||||
OrderTransactionStates::STATE_OPEN,
|
OrderTransactionStates::STATE_OPEN,
|
||||||
OrderTransactionStates::STATE_IN_PROGRESS,
|
OrderTransactionStates::STATE_IN_PROGRESS,
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
|
|
||||||
if (!$isOrderTransactionStateOpen) { // order payment status is open or in progress
|
if (!$isOrderTransactionStateOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param CheckoutConfirmPageLoadedEvent $event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function onCheckoutConfirmLoaded(CheckoutConfirmPageLoadedEvent $event): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$salesChannelContext = $event->getSalesChannelContext();
|
|
||||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
|
||||||
if (is_null($settings)) {
|
|
||||||
$this->logger->notice('Removing payment methods because settings are invalid');
|
|
||||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
|
||||||
}
|
|
||||||
|
|
||||||
$createdTransactionId = $this->transactionService->createPendingTransaction($salesChannelContext, $event);
|
|
||||||
$this->updateTempTransactionIfNeeded($salesChannelContext, $createdTransactionId);
|
|
||||||
|
|
||||||
$this->getAvailablePaymentMethods($settings, $createdTransactionId, $salesChannelContext);
|
|
||||||
$this->setPossiblePaymentMethods($settings->getSpaceId(), $event);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param AccountEditOrderPageLoadedEvent $event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function onAccountOrderEditLoaded(AccountEditOrderPageLoadedEvent $event): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->handlePaymentMethodFiltering($event);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param AccountPaymentMethodPageLoadedEvent $event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function onAccountPaymentMethodLoaded(AccountPaymentMethodPageLoadedEvent $event): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->handlePaymentMethodFiltering($event);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent $event
|
|
||||||
*/
|
|
||||||
public function onConfirmPageLoaded(CheckoutConfirmPageLoadedEvent $event): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->handlePaymentMethodFiltering($event);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->logger->error($e->getMessage());
|
|
||||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function handlePaymentMethodFiltering($event): void
|
|
||||||
{
|
|
||||||
$salesChannelContext = $event->getSalesChannelContext();
|
|
||||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
|
||||||
|
|
||||||
if (is_null($settings)) {
|
|
||||||
$this->logger->notice('Removing payment methods because settings are invalid');
|
|
||||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$createdTransactionId = $this->transactionService->createPendingTransaction($salesChannelContext, $event);
|
|
||||||
$this->updateTempTransactionIfNeeded($salesChannelContext, $createdTransactionId);
|
|
||||||
|
|
||||||
$this->getAvailablePaymentMethods($settings, $createdTransactionId, $salesChannelContext);
|
|
||||||
$this->setPossiblePaymentMethods($settings->getSpaceId(), $event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function removeVRPaymentPaymentMethodFromConfirmPage($event): void
|
|
||||||
{
|
|
||||||
$paymentMethodCollection = $event->getPage()->getPaymentMethods();
|
|
||||||
$paymentMethodIds = $this->paymentMethodUtil->getVRPaymentPaymentMethodIds($event->getContext());
|
|
||||||
foreach ($paymentMethodIds as $paymentMethodId) {
|
|
||||||
$paymentMethodCollection->remove($paymentMethodId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Settings $settings
|
|
||||||
* @param int $createdTransactionId
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function getAvailablePaymentMethods(Settings $settings, int $createdTransactionId, SalesChannelContext $salesChannelContext): void
|
|
||||||
{
|
|
||||||
$transactionService = $settings->getApiClient()->getTransactionService();
|
|
||||||
$possiblePaymentMethods = $transactionService->fetchPaymentMethods(
|
|
||||||
$settings->getSpaceId(),
|
|
||||||
$createdTransactionId,
|
|
||||||
$settings->getIntegration()
|
|
||||||
);
|
|
||||||
$arrayOfPossibleMethods = [];
|
|
||||||
foreach ($possiblePaymentMethods as $possiblePaymentMethod) {
|
|
||||||
$arrayOfPossibleMethods[] = $possiblePaymentMethod->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
$salesChannelContext->getContext()->addExtension(
|
|
||||||
'possibleMethods',
|
|
||||||
new ArrayEntity(['ids' => $arrayOfPossibleMethods])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $spaceId
|
|
||||||
* @param CheckoutConfirmPageLoadedEvent $event
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function setPossiblePaymentMethods(int $spaceId, $event): void
|
|
||||||
{
|
|
||||||
$paymentIds = [];
|
|
||||||
$paymentMethodCollection = $event->getPage()->getPaymentMethods();
|
|
||||||
|
|
||||||
foreach ($paymentMethodCollection as $paymentMethodCollectionItem) {
|
|
||||||
$isVRPaymentPM = VRPaymentPaymentHandler::class === $paymentMethodCollectionItem->getHandlerIdentifier();
|
|
||||||
if (!$isVRPaymentPM) {
|
|
||||||
$paymentIds[] = $paymentMethodCollectionItem->getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$allowedWLMethods = [];
|
|
||||||
$paymentMethodConfigurations = $this->paymentMethodConfigurationService
|
|
||||||
->getAllPaymentMethodConfigurations($spaceId, $event->getSalesChannelContext()->getContext());
|
|
||||||
|
|
||||||
foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) {
|
|
||||||
if ($paymentMethodConfiguration->getPaymentMethod() === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$pmId = $paymentMethodConfiguration->getPaymentMethod()->getId();
|
|
||||||
$pmConfigId = $paymentMethodConfiguration->getPaymentMethodConfigurationId();
|
|
||||||
$allowedIds = $this->getAllowedPaymentMethodIds($event->getSalesChannelContext());
|
|
||||||
|
|
||||||
if ($paymentMethodConfiguration->getSpaceId() === $spaceId
|
|
||||||
&& \in_array($pmConfigId, $allowedIds, true)) {
|
|
||||||
$allowedWLMethods[] = $pmId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$allPaymentIds = array_unique(array_merge($paymentIds, $allowedWLMethods));
|
|
||||||
$collection = new PaymentMethodCollection();
|
|
||||||
if (!empty($allPaymentIds)) {
|
|
||||||
$criteria = new Criteria($allPaymentIds);
|
|
||||||
$criteria->addFilter(new EqualsFilter('active', true));
|
|
||||||
$criteria->addFilter(
|
|
||||||
new EqualsFilter('salesChannels.id', $event->getSalesChannelContext()->getSalesChannelId())
|
|
||||||
);
|
|
||||||
$criteria->addSorting(new FieldSorting('position', FieldSorting::ASCENDING));
|
|
||||||
|
|
||||||
$result = $this->paymentMethodRepository->search($criteria, $event->getContext());
|
|
||||||
foreach ($result->getEntities() as $method) {
|
|
||||||
if (!$collection->has($method->getId())) {
|
|
||||||
$collection->add($method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$event->getPage()->setPaymentMethods($collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SalesChannelContext $salesChannelContext
|
|
||||||
* @param int $createdTransactionId
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function updateTempTransactionIfNeeded(SalesChannelContext $salesChannelContext, int $createdTransactionId): void
|
|
||||||
{
|
|
||||||
$ctx = $salesChannelContext->getContext();
|
|
||||||
|
|
||||||
/** @var ArrayEntity|null $ext */
|
|
||||||
$ext = $ctx->getExtension('checkoutState');
|
|
||||||
|
|
||||||
$oldAddressHash = $ext instanceof ArrayEntity ? $ext->get('addressHash') : null;
|
|
||||||
$oldCurrency = $ext instanceof ArrayEntity ? $ext->get('currency') : null;
|
|
||||||
|
|
||||||
$customer = $salesChannelContext->getCustomer();
|
|
||||||
$addressHash = md5(json_encode((array) $customer));
|
|
||||||
$currency = $salesChannelContext->getCurrency()->getIsoCode();
|
|
||||||
|
|
||||||
$needsUpdate = ($oldAddressHash !== $addressHash) || ($oldCurrency !== $currency);
|
|
||||||
|
|
||||||
if ($needsUpdate) {
|
|
||||||
if ($createdTransactionId) {
|
|
||||||
$this->transactionService->updateTempTransaction($salesChannelContext, $createdTransactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$ctx->addExtension('possibleMethods', new ArrayEntity(['ids' => []]));
|
|
||||||
$ctx->addExtension(
|
|
||||||
'checkoutState',
|
|
||||||
new ArrayEntity([
|
|
||||||
'addressHash' => $addressHash,
|
|
||||||
'currency' => $currency,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SalesChannelContext $salesChannelContext
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function getAllowedPaymentMethodIds(SalesChannelContext $salesChannelContext): array
|
|
||||||
{
|
|
||||||
$ext = $salesChannelContext->getContext()->getExtension('possibleMethods');
|
|
||||||
return $ext instanceof ArrayEntity ? ($ext->get('ids') ?? []) : [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Analytics {
|
|||||||
self::SHOP_SYSTEM => 'shopware',
|
self::SHOP_SYSTEM => 'shopware',
|
||||||
self::SHOP_SYSTEM_VERSION => '6',
|
self::SHOP_SYSTEM_VERSION => '6',
|
||||||
self::SHOP_SYSTEM_AND_VERSION => 'shopware-6',
|
self::SHOP_SYSTEM_AND_VERSION => 'shopware-6',
|
||||||
self::PLUGIN_SYSTEM_VERSION => '7.2.0',
|
self::PLUGIN_SYSTEM_VERSION => '7.3.0',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace VRPaymentPayment\Core\Util\Payload;
|
namespace VRPaymentPayment\Core\Util\Payload;
|
||||||
|
|
||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Shopware\Core\{Checkout\Cart\Tax\Struct\CalculatedTaxCollection,
|
use Shopware\Core\{
|
||||||
|
Checkout\Cart\Tax\Struct\CalculatedTaxCollection,
|
||||||
Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity,
|
Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity,
|
||||||
Checkout\Customer\CustomerEntity,
|
Checkout\Customer\CustomerEntity,
|
||||||
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity,
|
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity,
|
||||||
@@ -15,10 +18,12 @@ use Shopware\Core\{Checkout\Cart\Tax\Struct\CalculatedTaxCollection,
|
|||||||
};
|
};
|
||||||
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
|
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
|
||||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
|
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
|
||||||
|
use Shopware\Core\Framework\Api\Context\SalesChannelApiSource;
|
||||||
|
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use VRPayment\Sdk\{Model\AddressCreate,
|
use VRPayment\Sdk\{
|
||||||
|
Model\AddressCreate,
|
||||||
Model\ChargeAttempt,
|
Model\ChargeAttempt,
|
||||||
Model\CreationEntityState,
|
Model\CreationEntityState,
|
||||||
Model\CriteriaOperator,
|
Model\CriteriaOperator,
|
||||||
@@ -32,7 +37,8 @@ use VRPayment\Sdk\{Model\AddressCreate,
|
|||||||
Model\TransactionCreate,
|
Model\TransactionCreate,
|
||||||
Model\TransactionPending
|
Model\TransactionPending
|
||||||
};
|
};
|
||||||
use VRPaymentPayment\Core\{Api\PaymentMethodConfiguration\Entity\PaymentMethodConfigurationEntity,
|
use VRPaymentPayment\Core\{
|
||||||
|
Api\PaymentMethodConfiguration\Entity\PaymentMethodConfigurationEntity,
|
||||||
Settings\Struct\Settings,
|
Settings\Struct\Settings,
|
||||||
Util\Exception\InvalidPayloadException,
|
Util\Exception\InvalidPayloadException,
|
||||||
Util\LocaleCodeProvider,
|
Util\LocaleCodeProvider,
|
||||||
@@ -98,6 +104,16 @@ class TransactionPayload extends AbstractPayload
|
|||||||
|
|
||||||
protected OrderEntity $order;
|
protected OrderEntity $order;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $transactionId;
|
||||||
|
|
||||||
|
public function setTransactionId(int $transactionId): void
|
||||||
|
{
|
||||||
|
$this->transactionId = $transactionId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TransactionPayload constructor.
|
* TransactionPayload constructor.
|
||||||
*
|
*
|
||||||
@@ -113,8 +129,7 @@ class TransactionPayload extends AbstractPayload
|
|||||||
SalesChannelContext $salesChannelContext,
|
SalesChannelContext $salesChannelContext,
|
||||||
Settings $settings,
|
Settings $settings,
|
||||||
PaymentTransactionStruct $transaction
|
PaymentTransactionStruct $transaction
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
$this->localeCodeProvider = $localeCodeProvider;
|
$this->localeCodeProvider = $localeCodeProvider;
|
||||||
$this->salesChannelContext = $salesChannelContext;
|
$this->salesChannelContext = $salesChannelContext;
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
@@ -218,7 +233,7 @@ class TransactionPayload extends AbstractPayload
|
|||||||
}
|
}
|
||||||
|
|
||||||
$transactionPayload = (new TransactionPending())
|
$transactionPayload = (new TransactionPending())
|
||||||
->setId($_SESSION['transactionId'])
|
->setId($this->transactionId)
|
||||||
->setVersion($version)
|
->setVersion($version)
|
||||||
->setBillingAddress($billingAddress)
|
->setBillingAddress($billingAddress)
|
||||||
->setCurrency($transactionData['currency'])
|
->setCurrency($transactionData['currency'])
|
||||||
@@ -243,7 +258,9 @@ class TransactionPayload extends AbstractPayload
|
|||||||
}
|
}
|
||||||
|
|
||||||
$successUrl = $this->transaction->getReturnUrl() . '&status=paid';
|
$successUrl = $this->transaction->getReturnUrl() . '&status=paid';
|
||||||
$failedUrl = $this->getFailUrl($this->order->getId()) . '&status=fail';
|
// For headless clients, use the returnUrl for failure as well (they handle the status parameter).
|
||||||
|
// For Storefront, use the recreate-cart route.
|
||||||
|
$failedUrl = $this->getFailUrl($this->order->getId(), $this->transaction->getReturnUrl()) . '&status=fail';
|
||||||
$transactionPayload->setSuccessUrl($successUrl)
|
$transactionPayload->setSuccessUrl($successUrl)
|
||||||
->setFailedUrl($failedUrl);
|
->setFailedUrl($failedUrl);
|
||||||
|
|
||||||
@@ -540,7 +557,6 @@ class TransactionPayload extends AbstractPayload
|
|||||||
$this->translator->trans('vrpayment.payload.taxes'),
|
$this->translator->trans('vrpayment.payload.taxes'),
|
||||||
$amount
|
$amount
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$productAttributes = $this->getProductAttributes($shopLineItem);
|
$productAttributes = $this->getProductAttributes($shopLineItem);
|
||||||
|
|
||||||
@@ -590,7 +606,7 @@ class TransactionPayload extends AbstractPayload
|
|||||||
throw new InvalidPayloadException('Tax payload invalid:' . json_encode($tax->listInvalidProperties()));
|
throw new InvalidPayloadException('Tax payload invalid:' . json_encode($tax->listInvalidProperties()));
|
||||||
}
|
}
|
||||||
|
|
||||||
$taxes [] = $tax;
|
$taxes[] = $tax;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $taxes;
|
return $taxes;
|
||||||
@@ -670,7 +686,6 @@ class TransactionPayload extends AbstractPayload
|
|||||||
|
|
||||||
return $lineItem;
|
return $lineItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage());
|
$this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage());
|
||||||
}
|
}
|
||||||
@@ -697,7 +712,7 @@ class TransactionPayload extends AbstractPayload
|
|||||||
$taxRate = $taxItem->getTaxRate();
|
$taxRate = $taxItem->getTaxRate();
|
||||||
$tax = (new TaxCreate())
|
$tax = (new TaxCreate())
|
||||||
->setRate($taxRate)
|
->setRate($taxRate)
|
||||||
->setTitle('Tax rate: '.$taxRate);
|
->setTitle('Tax rate: ' . $taxRate);
|
||||||
|
|
||||||
$roundedAmount = self::round($amount);
|
$roundedAmount = self::round($amount);
|
||||||
|
|
||||||
@@ -724,7 +739,6 @@ class TransactionPayload extends AbstractPayload
|
|||||||
}
|
}
|
||||||
return $lineItems;
|
return $lineItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage());
|
$this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage());
|
||||||
}
|
}
|
||||||
@@ -767,7 +781,6 @@ class TransactionPayload extends AbstractPayload
|
|||||||
]);
|
]);
|
||||||
$this->logger->critical($error);
|
$this->logger->critical($error);
|
||||||
throw new \Exception($error);
|
throw new \Exception($error);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$lineItem = (new LineItemCreate())
|
$lineItem = (new LineItemCreate())
|
||||||
->setName($this->translator->trans('vrpayment.payload.adjustmentLineItem'))
|
->setName($this->translator->trans('vrpayment.payload.adjustmentLineItem'))
|
||||||
@@ -849,7 +862,6 @@ class TransactionPayload extends AbstractPayload
|
|||||||
} else {
|
} else {
|
||||||
if (!empty($customer->getSalutation())) {
|
if (!empty($customer->getSalutation())) {
|
||||||
$salutation = $customer->getSalutation()->getDisplayName();
|
$salutation = $customer->getSalutation()->getDisplayName();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$salutation = !empty($salutation) ? $this->fixLength($salutation, 20) : null;
|
$salutation = !empty($salutation) ? $this->fixLength($salutation, 20) : null;
|
||||||
@@ -931,14 +943,48 @@ class TransactionPayload extends AbstractPayload
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get failure URL
|
* Generates the failure URL for payment transactions.
|
||||||
|
* For headless clients (Store API), returns the client's returnUrl.
|
||||||
|
* For Storefront, returns the recreate-cart route.
|
||||||
*
|
*
|
||||||
* @param string $orderId
|
* @param string $orderId The order ID for the Storefront route.
|
||||||
*
|
* @param string|null $returnUrl The client's return URL (used for headless).
|
||||||
* @return string
|
* @return string The failure URL.
|
||||||
*/
|
*/
|
||||||
protected function getFailUrl(string $orderId): string
|
protected function getFailUrl(string $orderId, ?string $returnUrl = null): string
|
||||||
{
|
{
|
||||||
|
// For headless clients (Store API) or custom integrations, we use the returnUrl so the client can handle the failure.
|
||||||
|
// We detect "Standard Storefront" usage by checking if the returnUrl matches the standard Shopware 'finalize-transaction' route.
|
||||||
|
// If it DOES match, we usually redirect to 'recreate-cart' for better error handling. (Storefront / Edit Order)
|
||||||
|
// If it DOES NOT match (e.g. custom URL, headless URL), we return it as is.
|
||||||
|
|
||||||
|
$isStandardShopwareUrl = strpos($returnUrl ?? '', 'payment/finalize-transaction') !== false;
|
||||||
|
|
||||||
|
$request = $this->container->get('request_stack')->getCurrentRequest();
|
||||||
|
$isJsonRequest = false;
|
||||||
|
if ($request) {
|
||||||
|
// Check for JSON preference
|
||||||
|
$format = $request->getPreferredFormat();
|
||||||
|
$contentTypes = $request->getAcceptableContentTypes();
|
||||||
|
$isJsonRequest = $format === 'json' || in_array('application/json', $contentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($returnUrl) {
|
||||||
|
// Case 1: Custom URL (Headless detection part 1)
|
||||||
|
// If the URL is custom (not standard Shopware), we always respect it.
|
||||||
|
if (!$isStandardShopwareUrl) {
|
||||||
|
return $returnUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Standard URL + JSON Request (Headless detection part 2)
|
||||||
|
// If URL is standard BUT request prefers JSON, it's likely a headless app (Store API) being proxied.
|
||||||
|
if ($isStandardShopwareUrl && $isJsonRequest) {
|
||||||
|
return $returnUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: Storefront (SystemSource, SalesChannelSource, or AdminSalesChannelApiSource via Edit Order/HTML)
|
||||||
|
// We generate the recreate-cart route to restore the cart and show the error.
|
||||||
return $this->container->get('router')->generate(
|
return $this->container->get('router')->generate(
|
||||||
'frontend.vrpayment.checkout.recreate-cart',
|
'frontend.vrpayment.checkout.recreate-cart',
|
||||||
['orderId' => $orderId,],
|
['orderId' => $orderId,],
|
||||||
|
|||||||
+5
-1
@@ -10,6 +10,9 @@ export const CONFIG_USER_ID = CONFIG_DOMAIN + '.' + 'userId';
|
|||||||
export const CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontWebhooksUpdateEnabled';
|
export const CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontWebhooksUpdateEnabled';
|
||||||
export const CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontPaymentsUpdateEnabled';
|
export const CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontPaymentsUpdateEnabled';
|
||||||
|
|
||||||
|
// References vendor/shopware/core/Defaults.php::SALES_CHANNEL_TYPE_STOREFRONT
|
||||||
|
export const STOREFRONT_SALES_CHANNEL_TYPE_ID = '8a243080f92e4c719546314b577cf82b';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
CONFIG_DOMAIN,
|
CONFIG_DOMAIN,
|
||||||
CONFIG_APPLICATION_KEY,
|
CONFIG_APPLICATION_KEY,
|
||||||
@@ -21,5 +24,6 @@ export default {
|
|||||||
CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED,
|
CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED,
|
||||||
CONFIG_USER_ID,
|
CONFIG_USER_ID,
|
||||||
CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED,
|
CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED,
|
||||||
CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED
|
CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED,
|
||||||
|
STOREFRONT_SALES_CHANNEL_TYPE_ID
|
||||||
};
|
};
|
||||||
+78
-5
@@ -3,7 +3,7 @@
|
|||||||
import template from './index.html.twig';
|
import template from './index.html.twig';
|
||||||
import constants from './configuration-constants';
|
import constants from './configuration-constants';
|
||||||
|
|
||||||
const {Component, Mixin} = Shopware;
|
const { Component, Mixin } = Shopware;
|
||||||
|
|
||||||
Component.register('vrpayment-settings', {
|
Component.register('vrpayment-settings', {
|
||||||
|
|
||||||
@@ -11,7 +11,8 @@ Component.register('vrpayment-settings', {
|
|||||||
|
|
||||||
inject: [
|
inject: [
|
||||||
'acl',
|
'acl',
|
||||||
'VRPaymentConfigurationService'
|
'VRPaymentConfigurationService',
|
||||||
|
'repositoryFactory'
|
||||||
],
|
],
|
||||||
|
|
||||||
mixins: [
|
mixins: [
|
||||||
@@ -161,21 +162,93 @@ Component.register('vrpayment-settings', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInheritValue(key) {
|
getInheritValue(key) {
|
||||||
if (this.selectedSalesChannelId == null ) {
|
if (this.selectedSalesChannelId == null) {
|
||||||
return this.actualConfigData[key];
|
return this.actualConfigData[key];
|
||||||
} else {
|
} else {
|
||||||
return this.allConfigs['null'][key];
|
return this.allConfigs['null'][key];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onSave() {
|
async onSave() {
|
||||||
if (!(this.spaceIdFilled && this.userIdFilled && this.applicationKeyFilled)) {
|
if (!(this.spaceIdFilled && this.userIdFilled && this.applicationKeyFilled)) {
|
||||||
this.setErrorStates();
|
this.setErrorStates();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
const validationError = await this.validateHeadlessIntegration();
|
||||||
|
|
||||||
|
if (validationError === 'HEADLESS') {
|
||||||
|
this.createNotificationError({
|
||||||
|
title: this.$tc('vrpayment-settings.settingForm.titleError'),
|
||||||
|
message: this.$tc('vrpayment-settings.settingForm.messageHeadlessIntegrationError')
|
||||||
|
});
|
||||||
|
this.isLoading = false;
|
||||||
|
return;
|
||||||
|
} else if (validationError === 'GLOBAL') {
|
||||||
|
this.createNotificationError({
|
||||||
|
title: this.$tc('vrpayment-settings.settingForm.titleError'),
|
||||||
|
message: this.$tc('vrpayment-settings.settingForm.messageGlobalIframeError')
|
||||||
|
});
|
||||||
|
this.isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async validateHeadlessIntegration() {
|
||||||
|
const salesChannelId = this.$refs.configComponent.selectedSalesChannelId;
|
||||||
|
const currentIntegration = this.config[this.CONFIG_INTEGRATION];
|
||||||
|
|
||||||
|
// If integration is 'payment_page', it is always valid.
|
||||||
|
if (currentIntegration === 'payment_page') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const salesChannelRepo = this.repositoryFactory.create('sales_channel');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (salesChannelId) {
|
||||||
|
// Specific Sales Channel Check
|
||||||
|
const salesChannel = await salesChannelRepo.get(salesChannelId, Shopware.Context.api);
|
||||||
|
|
||||||
|
const currentTypeId = salesChannel.typeId.replace(/-/g, '');
|
||||||
|
|
||||||
|
// REST-2: Inverted Logic
|
||||||
|
// We only allow 'iframe' integration if the Sales Channel is of type 'Storefront'.
|
||||||
|
// Any other type (Headless, Product Export, Custom, etc.) does not support Iframe injection.
|
||||||
|
const isStorefront = currentTypeId === constants.STOREFRONT_SALES_CHANNEL_TYPE_ID;
|
||||||
|
if (!isStorefront) {
|
||||||
|
return 'HEADLESS';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Global Scope ("All Sales Channels") Check
|
||||||
|
// We must check if there is ANY Sales Channel that is NOT Storefront.
|
||||||
|
// If so, we cannot allow "Iframe" globally, as it would break those channels.
|
||||||
|
const criteria = new Shopware.Data.Criteria();
|
||||||
|
criteria.addFilter(
|
||||||
|
Shopware.Data.Criteria.not(
|
||||||
|
'AND',
|
||||||
|
[Shopware.Data.Criteria.equals('typeId', constants.STOREFRONT_SALES_CHANNEL_TYPE_ID)]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
criteria.setLimit(1); // We only need to know if at least one exists
|
||||||
|
|
||||||
|
const result = await salesChannelRepo.search(criteria, Shopware.Context.api);
|
||||||
|
|
||||||
|
if (result.total > 0) {
|
||||||
|
return 'GLOBAL';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
@@ -235,7 +308,7 @@ Component.register('vrpayment-settings', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
installOrderDeliveryStates(){
|
installOrderDeliveryStates() {
|
||||||
this.VRPaymentConfigurationService.installOrderDeliveryStates()
|
this.VRPaymentConfigurationService.installOrderDeliveryStates()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.createNotificationSuccess({
|
this.createNotificationSuccess({
|
||||||
|
|||||||
@@ -56,6 +56,8 @@
|
|||||||
"messagePaymentMethodConfigurationUpdated": "VRPayment PaymentMethodConfiguration has been registered.",
|
"messagePaymentMethodConfigurationUpdated": "VRPayment PaymentMethodConfiguration has been registered.",
|
||||||
"messageWebHookError": "VRPayment WebHook could not be saved. Please check your credentials.",
|
"messageWebHookError": "VRPayment WebHook could not be saved. Please check your credentials.",
|
||||||
"messageWebHookUpdated": "VRPayment WebHook has been updated.",
|
"messageWebHookUpdated": "VRPayment WebHook has been updated.",
|
||||||
|
"messageHeadlessIntegrationError": "Iframe integration is only supported for Storefront Sales Channels.",
|
||||||
|
"messageGlobalIframeError": "Iframe integration cannot be set globally because you have non-Storefront Sales Channels.",
|
||||||
"options": {
|
"options": {
|
||||||
"cardTitle": "Options",
|
"cardTitle": "Options",
|
||||||
"emailEnabled": {
|
"emailEnabled": {
|
||||||
|
|||||||
+8
-1
File diff suppressed because one or more lines are too long
+154
-9
@@ -3,25 +3,170 @@
|
|||||||
// noinspection NpmUsedModulesInstalled
|
// noinspection NpmUsedModulesInstalled
|
||||||
import Plugin from 'src/plugin-system/plugin.class';
|
import Plugin from 'src/plugin-system/plugin.class';
|
||||||
import HttpClient from 'src/service/http-client.service';
|
import HttpClient from 'src/service/http-client.service';
|
||||||
|
import DomAccess from 'src/helper/dom-access.helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VRPaymentCheckoutPlugin
|
||||||
|
*
|
||||||
|
* This plugin handles the initialization and lifecycle of the WhitelabelMachineName payment iframe
|
||||||
|
* within the Shopware 6 checkout confirm page.
|
||||||
|
*/
|
||||||
class VRPaymentCheckoutPlugin extends Plugin {
|
class VRPaymentCheckoutPlugin extends Plugin {
|
||||||
|
|
||||||
static options = {
|
static options = {
|
||||||
payment_method_tabs: 'ul.vrpayment-payment-panel li',
|
payment_panel_id: 'vrpayment-payment-panel',
|
||||||
payment_method_iframe_prefix: 'iframe_payment_method_',
|
payment_method_iframe_id: 'vrpayment-payment-iframe',
|
||||||
payment_method_iframe_class: '.vrpayment-payment-iframe',
|
|
||||||
payment_method_handler_name: 'vrpayment_payment_handler',
|
|
||||||
payment_method_handler_prefix: 'vrpayment_handler_',
|
|
||||||
payment_method_handler_status: 'input[name="vrpayment_payment_handler_validation_status"]',
|
payment_method_handler_status: 'input[name="vrpayment_payment_handler_validation_status"]',
|
||||||
payment_form: 'confirmOrderForm',
|
payment_form_id: 'confirmOrderForm',
|
||||||
|
loader_id: 'vrpaymentLoader',
|
||||||
|
checkout_url_id: 'checkoutUrl',
|
||||||
|
cart_recreate_url_id: 'cartRecreateUrl',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the plugin, variables, and events.
|
||||||
|
*/
|
||||||
init() {
|
init() {
|
||||||
// @TODO Move JS to Plugin
|
try {
|
||||||
this._client = new HttpClient(window.accessKey);
|
this._initVariables();
|
||||||
|
this._registerEvents();
|
||||||
|
this._getIframe();
|
||||||
|
} catch (e) {
|
||||||
|
// Silently fail if elements are not found; this allows the plugin to be loaded on pages where it might be conditionally absent.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and stores references to relevant DOM elements.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initVariables() {
|
||||||
|
this.checkoutUrl = DomAccess.getElement(document, `#${this.options.checkout_url_id}`).value;
|
||||||
|
this.cartRecreateUrl = DomAccess.getElement(document, `#${this.options.cart_recreate_url_id}`).value;
|
||||||
|
this.paymentForm = DomAccess.getElement(document, `#${this.options.payment_form_id}`);
|
||||||
|
this.paymentPanel = this.el;
|
||||||
|
this.iframeContainer = DomAccess.getElement(this.paymentPanel, `#${this.options.payment_method_iframe_id}`);
|
||||||
|
this.handler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers event listeners for the payment form and browser history.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_registerEvents() {
|
||||||
|
this.paymentForm.addEventListener('submit', this._submitPayment.bind(this), false);
|
||||||
|
|
||||||
|
// Handle back button/popstate to ensure cart consistency
|
||||||
|
window.addEventListener('popstate', this._onPopstate.bind(this), false);
|
||||||
|
window.history.pushState({}, document.title, this.cartRecreateUrl);
|
||||||
|
window.history.pushState({}, document.title, this.checkoutUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles browser back button activity.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onPopstate() {
|
||||||
|
if (window.history.state == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.href = this.cartRecreateUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts the form submission to validate the iframe content first.
|
||||||
|
* @param {Event} event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_submitPayment(event) {
|
||||||
|
this._activateLoader(true);
|
||||||
|
if (this.handler) {
|
||||||
|
this.handler.validate();
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the WhitelabelMachineName Iframe handler and creates the iframe.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getIframe() {
|
||||||
|
const paymentMethodConfigurationId = this.paymentPanel.dataset.id;
|
||||||
|
|
||||||
|
if (!this.handler) {
|
||||||
|
// IframeCheckoutHandler is expected to be global from the SDK script
|
||||||
|
if (typeof window.IframeCheckoutHandler === 'function') {
|
||||||
|
this.handler = window.IframeCheckoutHandler(paymentMethodConfigurationId);
|
||||||
|
this.handler.setValidationCallback(this._validationCallBack.bind(this));
|
||||||
|
this.handler.setInitializeCallback(this._hideLoader.bind(this));
|
||||||
|
this.handler.setHeightChangeCallback((height) => {
|
||||||
|
if (height < 1) {
|
||||||
|
this.handler.submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.handler.create(this.iframeContainer);
|
||||||
|
setTimeout(this._hideLoader.bind(this), 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for iframe validation results.
|
||||||
|
* @param {Object} validationResult
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_validationCallBack(validationResult) {
|
||||||
|
const statusInputs = document.querySelectorAll(this.options.payment_method_handler_status);
|
||||||
|
if (validationResult.success) {
|
||||||
|
statusInputs.forEach(input => {
|
||||||
|
input.value = 'true';
|
||||||
|
});
|
||||||
|
this.handler.submit();
|
||||||
|
} else {
|
||||||
|
statusInputs.forEach(input => {
|
||||||
|
input.value = 'false';
|
||||||
|
});
|
||||||
|
this._activateLoader(false);
|
||||||
|
if (validationResult.errors) {
|
||||||
|
this._showErrors(validationResult.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables buttons on the page to indicate loading.
|
||||||
|
* @param {boolean} activate
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_activateLoader(activate) {
|
||||||
|
const buttons = document.querySelectorAll('button');
|
||||||
|
buttons.forEach(button => {
|
||||||
|
button.disabled = activate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the loader overlay once the iframe is ready.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_hideLoader() {
|
||||||
|
const loader = document.getElementById(this.options.loader_id);
|
||||||
|
if (loader && loader.parentNode) {
|
||||||
|
loader.parentNode.removeChild(loader);
|
||||||
|
}
|
||||||
|
this._activateLoader(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays validation errors to the user.
|
||||||
|
* @param {Array} errors
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_showErrors(errors) {
|
||||||
|
// Fallback to alert if no native Shopware mechanism is easily accessible here
|
||||||
|
alert(errors.join('\n'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VRPaymentCheckoutPlugin;
|
export default VRPaymentCheckoutPlugin;
|
||||||
@@ -13,7 +13,9 @@
|
|||||||
<import resource="./services/core/storefront/checkout.xml"/>
|
<import resource="./services/core/storefront/checkout.xml"/>
|
||||||
|
|
||||||
<import resource="./services/core/checkout.xml"/>
|
<import resource="./services/core/checkout.xml"/>
|
||||||
|
<import resource="./services/core/checkout_services.xml"/>
|
||||||
<import resource="./services/core/settings.xml"/>
|
<import resource="./services/core/settings.xml"/>
|
||||||
|
<import resource="./services/core/store_api.xml"/>
|
||||||
<import resource="./services/core/util.xml"/>
|
<import resource="./services/core/util.xml"/>
|
||||||
</imports>
|
</imports>
|
||||||
<services>
|
<services>
|
||||||
|
|||||||
@@ -58,6 +58,8 @@
|
|||||||
<argument type="service" id="service_container"/>
|
<argument type="service" id="service_container"/>
|
||||||
<argument type="service" id="VRPaymentPayment\Core\Util\LocaleCodeProvider"/>
|
<argument type="service" id="VRPaymentPayment\Core\Util\LocaleCodeProvider"/>
|
||||||
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
||||||
|
<!-- Cache for headless transaction persistence -->
|
||||||
|
<argument type="service" id="cache.system"/>
|
||||||
<call method="setLogger">
|
<call method="setLogger">
|
||||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||||
</call>
|
</call>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<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="VRPaymentPayment\Core\Checkout\Service\TransactionManagementService">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
||||||
|
<!-- Cache for headless transaction persistence -->
|
||||||
|
<argument type="service" id="cache.system"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="VRPaymentPayment\Core\Checkout\Service\PaymentMethodFilterService">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Util\PaymentMethodUtil"/>
|
||||||
|
<argument type="service" id="payment_method.repository"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\TransactionManagementService"/>
|
||||||
|
<argument type="service" id="Shopware\Core\Checkout\Cart\SalesChannel\CartService"/>
|
||||||
|
<call method="setLogger">
|
||||||
|
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||||
|
</call>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="VRPaymentPayment\Core\Checkout\Service\PaymentIntegrationService">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\TransactionManagementService"/>
|
||||||
|
<argument type="service" id="router"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="VRPaymentPayment\Core\Checkout\Service\CartRecoveryService">
|
||||||
|
<argument type="service" id="Shopware\Core\Checkout\Cart\SalesChannel\CartService"/>
|
||||||
|
<argument type="service" id="Shopware\Core\Checkout\Cart\LineItemFactoryRegistry"/>
|
||||||
|
<argument type="service" id="order.repository"/>
|
||||||
|
<argument type="service" id="Swag\CustomizedProducts\Core\Checkout\Cart\Route\AddCustomizedProductsToCartRoute" on-invalid="null"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="VRPaymentPayment\Core\Checkout\Service\InvoiceService">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||||
|
</service>
|
||||||
|
</services>
|
||||||
|
</container>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<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="VRPaymentPayment\Core\Checkout\PaymentMethod\SalesChannel\PaymentMethodRouteDecorator"
|
||||||
|
decorates="Shopware\Core\Checkout\Payment\SalesChannel\PaymentMethodRoute"
|
||||||
|
decoration-inner-name="VRPaymentPayment\Core\Checkout\PaymentMethod\SalesChannel\PaymentMethodRouteDecorator.inner">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\PaymentMethod\SalesChannel\PaymentMethodRouteDecorator.inner"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\PaymentMethodFilterService"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="VRPaymentPayment\Core\Checkout\StoreApi\Route\WhitelabelMachineNameTransactionInfoRoute">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\PaymentIntegrationService"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="VRPaymentPayment\Core\Checkout\StoreApi\Route\CartRecoveryRoute">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\CartRecoveryService"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="VRPaymentPayment\Core\Checkout\StoreApi\Route\InvoiceRoute">
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\InvoiceService"/>
|
||||||
|
</service>
|
||||||
|
</services>
|
||||||
|
</container>
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
<services>
|
<services>
|
||||||
<!-- Controllers -->
|
<!-- Controllers -->
|
||||||
<service id="VRPaymentPayment\Core\Storefront\Account\Controller\AccountOrderController" public="true">
|
<service id="VRPaymentPayment\Core\Storefront\Account\Controller\AccountOrderController" public="true">
|
||||||
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
|
||||||
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||||
<argument type="service" id="Symfony\Component\HttpFoundation\RequestStack"/>
|
<argument type="service" id="Symfony\Component\HttpFoundation\RequestStack"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\InvoiceService"/>
|
||||||
<call method="setLogger">
|
<call method="setLogger">
|
||||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||||
</call>
|
</call>
|
||||||
|
|||||||
@@ -7,31 +7,25 @@
|
|||||||
<services>
|
<services>
|
||||||
<!-- Controllers -->
|
<!-- Controllers -->
|
||||||
<service id="VRPaymentPayment\Core\Storefront\Checkout\Controller\CheckoutController" public="true">
|
<service id="VRPaymentPayment\Core\Storefront\Checkout\Controller\CheckoutController" public="true">
|
||||||
<argument type="service" id="Shopware\Core\Checkout\Cart\LineItemFactoryRegistry"/>
|
|
||||||
<argument type="service" id="Shopware\Core\Checkout\Cart\SalesChannel\CartService"/>
|
|
||||||
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
||||||
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
|
||||||
<argument type="service" id="Shopware\Storefront\Page\GenericPageLoader"/>
|
<argument type="service" id="Shopware\Storefront\Page\GenericPageLoader"/>
|
||||||
<argument type="service" id="Shopware\Core\Checkout\Order\SalesChannel\OrderRoute"/>
|
<argument type="service" id="Shopware\Core\Checkout\Order\SalesChannel\OrderRoute"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\CartRecoveryService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Checkout\Service\PaymentIntegrationService"/>
|
||||||
|
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||||
<call method="setLogger">
|
<call method="setLogger">
|
||||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||||
</call>
|
</call>
|
||||||
<call method="setContainer">
|
<call method="setContainer">
|
||||||
<argument type="service" id="service_container"/>
|
<argument type="service" id="service_container"/>
|
||||||
</call>
|
</call>
|
||||||
<!-- Removed in 6.7 -->
|
|
||||||
<!-- <call method="setTwig">
|
|
||||||
<argument type="service" id="twig"/>
|
|
||||||
</call> -->
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- Subscribers -->
|
<!-- Subscribers -->
|
||||||
<service id="VRPaymentPayment\Core\Storefront\Checkout\Subscriber\CheckoutSubscriber">
|
<service id="VRPaymentPayment\Core\Storefront\Checkout\Subscriber\CheckoutSubscriber">
|
||||||
<argument id="VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService" type="service"/>
|
|
||||||
<argument id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService" type="service"/>
|
|
||||||
<argument id="VRPaymentPayment\Core\Settings\Service\SettingsService" type="service"/>
|
<argument id="VRPaymentPayment\Core\Settings\Service\SettingsService" type="service"/>
|
||||||
<argument id="VRPaymentPayment\Core\Util\PaymentMethodUtil" type="service"/>
|
<argument id="VRPaymentPayment\Core\Checkout\Service\PaymentMethodFilterService" type="service"/>
|
||||||
<argument id="payment_method.repository" type="service"/>
|
<argument id="VRPaymentPayment\Core\Checkout\Service\PaymentIntegrationService" type="service"/>
|
||||||
<call method="setLogger">
|
<call method="setLogger">
|
||||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||||
</call>
|
</call>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
],
|
],
|
||||||
"dynamic": [],
|
"dynamic": [],
|
||||||
"js": [
|
"js": [
|
||||||
"/bundles/vrpaymentpayment/administration/assets/v-r-payment-payment-Cp2eQSV_.js"
|
"/bundles/vrpaymentpayment/administration/assets/v-r-payment-payment-BV391wJu.js"
|
||||||
],
|
],
|
||||||
"legacy": false,
|
"legacy": false,
|
||||||
"preload": []
|
"preload": []
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"main.js": {
|
"main.js": {
|
||||||
"file": "assets/v-r-payment-payment-Cp2eQSV_.js",
|
"file": "assets/v-r-payment-payment-BV391wJu.js",
|
||||||
"name": "v-r-payment-payment",
|
"name": "v-r-payment-payment",
|
||||||
"src": "main.js",
|
"src": "main.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
|
|||||||
|
{% sw_extends '@Storefront/storefront/component/payment/payment-method.html.twig' %}
|
||||||
|
|
||||||
|
{% block component_payment_method_description %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
{# Check if the payment method handler belongs to WhitelabelMachineName #}
|
||||||
|
{% if payment.handlerIdentifier == 'VRPaymentPayment\\Core\\Checkout\\PaymentHandler\\VRPaymentPaymentHandler' %}
|
||||||
|
{# Retrieve the runtime-attached configuration from the extension #}
|
||||||
|
{% set vrpConfig = payment.extensions.vrpayment_config %}
|
||||||
|
|
||||||
|
{% if vrpConfig %}
|
||||||
|
{% set paymentMethodConfigurationId = vrpConfig.paymentMethodConfigurationId %}
|
||||||
|
|
||||||
|
{# Include the iframe container template #}
|
||||||
|
{% sw_include '@VRPaymentPayment/storefront/component/payment/vrpayment_iframe.html.twig' with {
|
||||||
|
'paymentMethodConfigurationId': paymentMethodConfigurationId
|
||||||
|
} %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div id="vrpayment-payment-panel"
|
||||||
|
class="vrpayment-payment-panel"
|
||||||
|
data-vrpayment-checkout-plugin="true"
|
||||||
|
data-id="{{ paymentMethodConfigurationId }}">
|
||||||
|
<div id="vrpaymentLoader"><div></div></div>
|
||||||
|
<input value="false" type="hidden" name="vrpayment_payment_handler_validation_status"
|
||||||
|
form="confirmOrderForm">
|
||||||
|
<div id="vrpayment-payment-iframe"
|
||||||
|
class="vrpayment-payment-iframe"></div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{% sw_extends '@Storefront/storefront/page/checkout/confirm/index.html.twig' %}
|
||||||
|
|
||||||
|
{% block base_body_script %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
{# Provide WhitelabelMachineName integration data if available in page extensions #}
|
||||||
|
{% if page.extensions.vRPaymentData %}
|
||||||
|
{% set vrpData = page.extensions.vRPaymentData %}
|
||||||
|
|
||||||
|
{# Hidden inputs required by the JS components #}
|
||||||
|
<input type="hidden" id="cartRecreateUrl" value="{{ vrpData.cartRecreateUrl }}" />
|
||||||
|
<input type="hidden" id="checkoutUrl" value="{{ vrpData.checkoutUrl }}" />
|
||||||
|
|
||||||
|
{# Load WhitelabelMachineName SDK scripts #}
|
||||||
|
{% if vrpData.deviceJavascriptUrl %}
|
||||||
|
<script src="{{ vrpData.deviceJavascriptUrl }}" async="async"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% if vrpData.javascriptUrl %}
|
||||||
|
<script src="{{ vrpData.javascriptUrl }}"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# We don't need app.js if we use the modern plugin, but we might keep it for now if other pages depend on it.
|
||||||
|
However, for the confirm page, the modern plugin registered in main.js will take over
|
||||||
|
whenever it finds [data-vrpayment-checkout-plugin]. #}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user