release 7.1.0

This commit is contained in:
andrewrowanwallee
2025-09-17 11:08:33 +02:00
parent edf474acea
commit e47682eb87
159 changed files with 1234 additions and 447 deletions
@@ -499,6 +499,7 @@ class PaymentMethodConfigurationService {
'afterOrderEnabled' => true,
'active' => true,
'translations' => $this->getPaymentMethodConfigurationTranslation($paymentMethodConfiguration, $context),
'technicalName' => $paymentMethodConfiguration->getName(),
];
$data['mediaId'] = $this->upsertMedia($id, $paymentMethodConfiguration, $context);
@@ -115,7 +115,11 @@ class RefundController extends AbstractController
$apiClient = $settings->getApiClient();
$transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId);
$this->refundService->createRefundByAmount($transaction, $refundableAmount, $context);
$refund = $this->refundService->createRefundByAmount($transaction, $refundableAmount, $context);
if ($refund === null) {
return new Response('refundExceedsAmount', Response::HTTP_BAD_REQUEST);
}
return new Response(null, Response::HTTP_NO_CONTENT);
}
@@ -8,7 +8,7 @@ use Shopware\Core\{
Checkout\Cart\CartException,
Checkout\Cart\LineItem\LineItem,
Checkout\Order\OrderEntity,
Checkout\Payment\Cart\AsyncPaymentTransactionStruct,
Checkout\Payment\Cart\PaymentTransactionStruct,
Framework\Context,
Framework\DataAbstractionLayer\Search\Criteria,
Framework\DataAbstractionLayer\Search\Filter\EqualsFilter,
@@ -16,22 +16,23 @@ use Shopware\Core\{
};
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use VRPayment\Sdk\{
Model\AddressCreate,
Model\ChargeAttempt,
Model\CreationEntityState,
Model\CriteriaOperator,
Model\EntityQuery,
Model\EntityQueryFilter,
Model\EntityQueryFilterType,
Model\Gender,
Model\LineItemAttributeCreate,
Model\LineItemCreate,
Model\LineItemType,
Model\Transaction,
Model\TransactionCreate,
Model\TransactionPending,
Model\TransactionState,
use VRPayment\Sdk\Model\{
AddressCreate,
ChargeAttempt,
CreationEntityState,
CriteriaOperator,
EntityQuery,
EntityQueryFilter,
EntityQueryFilterType,
Gender,
LineItemAttributeCreate,
LineItemCreate,
LineItemType,
TokenizationMode,
Transaction,
TransactionCreate,
TransactionPending,
TransactionState,
};
use VRPaymentPayment\Core\{
Api\OrderDeliveryState\Handler\OrderDeliveryStateHandler,
@@ -115,7 +116,7 @@ class TransactionService
*
* A redirect to the url will be performed
*
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
*
* @return string
@@ -124,10 +125,14 @@ class TransactionService
* @throws \VRPayment\Sdk\VersioningException
*/
public function create(
AsyncPaymentTransactionStruct $transaction,
SalesChannelContext $salesChannelContext
PaymentTransactionStruct $transaction,
SalesChannelContext $salesChannelContext
): string
{
$criteria = new Criteria([$transaction->getOrderTransactionId()]);
$criteria->addAssociation('order');
$orderTransaction = $this->container->get('order_transaction.repository')->search($criteria, $salesChannelContext->getContext())->first();
$salesChannelId = $salesChannelContext->getSalesChannel()->getId();
$settings = $this->settingsService->getSettings($salesChannelId);
$apiClient = $settings->getApiClient();
@@ -165,7 +170,7 @@ class TransactionService
$redirectUrl = $this->container->get('router')->generate(
'frontend.vrpayment.checkout.pay',
['orderId' => $transaction->getOrder()->getId(),],
['orderId' => $orderTransaction->getOrder()->getId(),],
UrlGeneratorInterface::ABSOLUTE_URL
);
@@ -177,8 +182,8 @@ class TransactionService
$this->upsert(
$createdTransaction,
$salesChannelContext->getContext(),
$transaction->getOrderTransaction()->getPaymentMethodId(),
$transaction->getOrder()->getSalesChannelId()
$orderTransaction->getPaymentMethodId(),
$orderTransaction->getOrder()->getSalesChannelId()
);
$_SESSION['transactionId'] = null;
$_SESSION['arrayOfPossibleMethods'] = null;
@@ -186,26 +191,45 @@ class TransactionService
$_SESSION['currencyCheck'] = null;
$this->holdDelivery($transaction->getOrder()->getId(), $salesChannelContext->getContext());
$this->holdDelivery($orderTransaction->getOrder()->getId(), $salesChannelContext->getContext());
return $redirectUrl;
}
/**
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* Creates the transaction in the portal using the SDK.
*
* @return void
*/
public function createRecurringTransaction(TransactionCreate $sdkTransactionCreate, string $spaceId = ""): Transaction {
$settings = $this->settingsService->getSettings();
if (empty($spaceId)) {
$spaceId = $settings->getSpaceId();
}
$sdkTransaction = $settings->getApiClient()->getTransactionService()->create($spaceId, $sdkTransactionCreate);
if ($sdkTransaction->valid()) {
return $settings->getApiClient()->getTransactionService()->processWithoutUserInteraction($spaceId, $sdkTransaction->getId());
}
throw new \Exception("The transacion is not valid and could not be created.");
}
/**
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Context $context
* @param int $vrpaymentTransactionId
* @param int $spaceId
*/
protected function addVRPaymentTransactionId(
AsyncPaymentTransactionStruct $transaction,
PaymentTransactionStruct $transaction,
Context $context,
int $vrpaymentTransactionId,
int $spaceId
): void
{
$data = [
'id' => $transaction->getOrderTransaction()->getId(),
'id' => $transaction->getOrderTransactionId(),
'customFields' => [
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID => $vrpaymentTransactionId,
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID => $spaceId,
@@ -343,7 +367,7 @@ class TransactionService
*
* @return \Shopware\Core\Checkout\Order\OrderEntity
*/
private function getOrderEntity(string $orderId, Context $context): OrderEntity
protected function getOrderEntity(string $orderId, Context $context): OrderEntity
{
try {
$criteria = (new Criteria([$orderId]))->addAssociations(['deliveries']);
@@ -387,7 +411,7 @@ class TransactionService
* @throws \VRPayment\Sdk\Http\ConnectionException
* @throws \VRPayment\Sdk\VersioningException
*/
public function read(int $transactionId, string $salesChannelId): Transaction
public function read(int $transactionId, string $salesChannelId = ""): Transaction
{
$settings = $this->settingsService->getSettings($salesChannelId);
return $settings->getApiClient()->getTransactionService()->read($settings->getSpaceId(), $transactionId);
@@ -594,7 +618,8 @@ class TransactionService
->setCustomerEmailAddress($customer->getEmail())
->setCustomerId($customerId)
->setSuccessUrl($homeUrl . '?success')
->setFailedUrl($homeUrl . '?fail');
->setFailedUrl($homeUrl . '?fail')
->setTokenizationMode(TokenizationMode::FORCE_CREATION);
$transactionService = $settings->getApiClient()->getTransactionService();
$transaction = $transactionService->create($settings->getSpaceId(), $transactionPayload);
@@ -9,12 +9,13 @@ use Shopware\Core\{
Checkout\Cart\CartException,
Framework\Context,
System\StateMachine\Exception\IllegalTransitionException};
use VRPayment\Sdk\{
Model\RefundState,
Model\Transaction,
Model\TransactionInvoiceState,
Model\TransactionState,
Model\TransactionInvoice,};
use VRPayment\Sdk\Model\{
RefundState,
Transaction,
TransactionInvoiceState,
TransactionState,
TransactionInvoice,
Token};
use VRPaymentPayment\Core\{
Api\WebHooks\Service\WebHooksService,
Api\WebHooks\Struct\WebHookRequest,
@@ -51,7 +52,7 @@ class WebHookTransactionStrategy extends WebHookStrategyBase implements WebhookS
* @throws \VRPayment\Sdk\Http\ConnectionException ConnectionException.
* @throws \VRPayment\Sdk\VersioningException VersioningException.
*/
public function getTransaction(WebHookRequest $request) {
public function getTransaction(WebHookRequest $request): Transaction {
return $this->settings->getApiClient()
->getTransactionService()
->read($request->getSpaceId(), $request->getEntityId());
@@ -60,7 +61,7 @@ class WebHookTransactionStrategy extends WebHookStrategyBase implements WebhookS
/**
* @inheritDoc
*/
public function getOrderIdByTransaction($transaction): string
public function getOrderIdByTransaction(Transaction $transaction): string
{
/** @var \VRPayment\Sdk\Model\Transaction $transaction */
return $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID];
@@ -96,7 +97,7 @@ class WebHookTransactionStrategy extends WebHookStrategyBase implements WebhookS
*/
public function process(WebHookRequest $request): Response
{
return $this->updateTransaction($request, $this->getContext());
return $this->processTransaction($request, $this->getContext());
}
/**
@@ -107,16 +108,17 @@ class WebHookTransactionStrategy extends WebHookStrategyBase implements WebhookS
* @param Context $context The operational context providing settings and environment for transaction processing.
* @return Response Returns a JSON response indicating the result of the transaction update operation.
*/
private function updateTransaction(WebHookRequest $request, Context $context): Response
private function processTransaction(WebHookRequest $request, Context $context): Response
{
$status = Response::HTTP_UNPROCESSABLE_ENTITY;
try {
/** @var \Shopware\Core\Checkout\Order\OrderEntity $order */
$transaction = $this->getTransaction($request);
$orderId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID];
if(!empty($orderId) && !$transaction->getParent()) {
$this->executeLocked($orderId, $context, function () use ($orderId, $transaction, $context, $request) {
$token = $transaction->getToken();
$orderId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID];
if (!empty($orderId) && !$transaction->getParent()) {
$this->executeLocked($orderId, $context, function () use ($orderId, $transaction, $context, $request, $token) {
$this->transactionService->upsert($transaction, $context);
$orderTransactionId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID];
$orderTransaction = $this->getOrderTransaction($orderId, $context);
@@ -143,6 +145,16 @@ class WebHookTransactionStrategy extends WebHookStrategyBase implements WebhookS
$this->unholdDelivery($orderId, $context);
break;
case TransactionState::AUTHORIZED:
if ($token instanceof Token) {
// Update orderTransaction with the authorized token:
$data = [
'id' => $orderTransactionId,
'customFields' => [
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TOKEN => $token->getId(),
],
];
$this->container->get('order_transaction.repository')->update([$data], $context);
}
$this->orderTransactionStateHandler->process($orderTransactionId, $context);
$this->sendEmail($transaction, $context, $orderId);
break;
@@ -57,5 +57,5 @@ interface WebhookStrategyActionsInterface {
* @param Transaction|TransactionInvoiceState|Refund|mixed $transaction The transaction object from which the order ID should be extracted.
* @return string The order ID as a string.
*/
public function getOrderIdByTransaction($transaction): string;
public function getOrderIdByTransaction(Transaction $transaction): string;
}
@@ -0,0 +1,59 @@
<?php declare(strict_types=1);
namespace VRPaymentPayment\Core\Checkout\Cart;
use Shopware\Core\Checkout\Cart\AbstractCartPersister;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use VRPaymentPayment\Core\Checkout\PaymentHandler\VRPaymentPaymentHandler;
class CustomCartPersister extends AbstractCartPersister
{
private AbstractCartPersister $inner;
public function __construct(AbstractCartPersister $inner)
{
$this->inner = $inner;
}
public function delete(string $token, SalesChannelContext $context): void
{
if (!$context->getContext()->hasState('do-cart-delete') && $this->isWhiteLabelPayment($context)) {
return;
}
$this->inner->delete($token, $context);
}
public function load(string $token, SalesChannelContext $context): Cart
{
return $this->inner->load($token, $context);
}
public function save(Cart $cart, SalesChannelContext $context): void
{
$this->inner->save($cart, $context);
}
public function replace(string $oldToken, string $newToken, SalesChannelContext $context): void
{
$this->inner->replace($oldToken, $newToken, $context);
}
public function getDecorated(): AbstractCartPersister
{
return $this->inner;
}
private function isWhiteLabelPayment(SalesChannelContext $context): bool
{
$paymentMethod = $context->getPaymentMethod();
if (!$paymentMethod instanceof PaymentMethodEntity) {
return false;
}
return $paymentMethod->getHandlerIdentifier() === VRPaymentPaymentHandler::class;
}
}
@@ -4,22 +4,42 @@ namespace VRPaymentPayment\Core\Checkout\PaymentHandler;
use Psr\Log\LoggerInterface;
use Shopware\Core\{
Checkout\Order\OrderEntity,
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity,
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler,
Checkout\Payment\Cart\AsyncPaymentTransactionStruct,
Checkout\Payment\Cart\PaymentHandler\AsynchronousPaymentHandlerInterface,
Checkout\Payment\Exception\AsyncPaymentFinalizeException,
Checkout\Payment\Exception\AsyncPaymentProcessException,
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates,
Checkout\Payment\Cart\PaymentTransactionStruct,
Checkout\Payment\Cart\PaymentHandler\AbstractPaymentHandler,
Checkout\Payment\Cart\PaymentHandler\PaymentHandlerType,
Checkout\Payment\PaymentException,
Checkout\Payment\Exception\CustomerCanceledAsyncPaymentException,
Framework\App\AppException,
Framework\Api\Context\SalesChannelApiSource,
Framework\Context,
Framework\DataAbstractionLayer\EntityRepository,
Framework\DataAbstractionLayer\Search\Criteria,
Framework\DataAbstractionLayer\Search\Filter\EqualsFilter,
Framework\DataAbstractionLayer\Search\Sorting\FieldSorting,
Framework\Struct\Struct,
Framework\Validation\DataBag\RequestDataBag,
System\SalesChannel\SalesChannelContext
System\StateMachine\Aggregation\StateMachineState\StateMachineStateEntity,
System\SalesChannel\Context\SalesChannelContextService,
System\SalesChannel\Context\SalesChannelContextServiceParameters
};
use Shopware\Core\Framework\Util\Random;
use VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartPersister;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\{
HttpFoundation\RedirectResponse,
HttpFoundation\Request
};
use VRPayment\Sdk\Model\TransactionState;
use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService as PluginTransactionService;
use VRPaymentPayment\Core\Util\Payload\TransactionPayload;
/**
@@ -27,13 +47,18 @@ use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
*
* @package VRPaymentPayment\Core\Checkout\PaymentHandler
*/
class VRPaymentPaymentHandler implements AsynchronousPaymentHandlerInterface
class VRPaymentPaymentHandler extends AbstractPaymentHandler
{
/**
* @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService
* @var CustomCartPersister
*/
protected $transactionService;
private CustomCartPersister $cartPersister;
/**
* @var \VRPaymentPayment\Core\Api\Transaction\Service\PluginTransactionService
*/
protected $pluginTransactionService;
/**
* @var \Psr\Log\LoggerInterface
@@ -44,22 +69,32 @@ class VRPaymentPaymentHandler implements AsynchronousPaymentHandlerInterface
*/
private $orderTransactionStateHandler;
/**
* VRPaymentPaymentHandler constructor.
*
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
* @param \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler $orderTransactionStateHandler
*/
public function __construct(TransactionService $transactionService, OrderTransactionStateHandler $orderTransactionStateHandler)
{
$this->transactionService = $transactionService;
$this->orderTransactionStateHandler = $orderTransactionStateHandler;
}
protected SalesChannelContextService $salesChannelContextService;
protected EntityRepository $orderTransactionRepository;
protected ?EntityRepository $subscriptionRepository;
/**
* VRPaymentPaymentHandler constructor.
*/
public function __construct(
CustomCartPersister $cartPersister,
PluginTransactionService $pluginTransactionService,
OrderTransactionStateHandler $orderTransactionStateHandler,
SalesChannelContextService $salesChannelContextService,
EntityRepository $orderTransactionRepository,
?EntityRepository $subscriptionRepository,
) {
$this->cartPersister = $cartPersister;
$this->pluginTransactionService = $pluginTransactionService;
$this->orderTransactionStateHandler = $orderTransactionStateHandler;
$this->salesChannelContextService = $salesChannelContextService;
$this->orderTransactionRepository = $orderTransactionRepository;
$this->subscriptionRepository = $subscriptionRepository;
}
/**
* @param \Psr\Log\LoggerInterface $logger
* @internal
* @required
*
*/
public function setLogger(LoggerInterface $logger): void
@@ -73,78 +108,352 @@ class VRPaymentPaymentHandler implements AsynchronousPaymentHandlerInterface
*
* A redirect to the url will be performed
*
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Validation\DataBag\RequestDataBag $dataBag
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
* @param \Symfony\Component\HttpFoundation\Request
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Context $context
* @param \Shopware\Core\Framework\Struct\Struct $validateStruct
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function pay(
AsyncPaymentTransactionStruct $transaction,
RequestDataBag $dataBag,
SalesChannelContext $salesChannelContext
Request $request,
PaymentTransactionStruct $transaction,
Context $context,
?Struct $validateStruct
): RedirectResponse
{
try {
$orderTransactionId = $transaction->getOrderTransactionId();
$orderTransaction = $this->orderTransactionRepository->search(
(new Criteria([$orderTransactionId]))
->addAssociation('order'), $context
)->getEntities()->first();
$contextSource = $context->getSource();
if ($contextSource instanceof SalesChannelApiSource) {
$salesChannelContextId = $contextSource->getSalesChannelId();
}
$parameters = new SalesChannelContextServiceParameters($salesChannelContextId, $request->getSession()->get("sw-context-token", Random::getAlphanumericString(32)), originalContext: $context);
$salesChannelContext = $this->salesChannelContextService->get($parameters);
$redirectUrl = $transaction->getReturnUrl();
if ($transaction->getOrder()->getAmountTotal() > 0) {
$transactionId = $_SESSION['transactionId'] ?? null;
if ($orderTransaction->getOrder()->getAmountTotal() > 0) {
$transactionId = $request->getSession()->get('transactionId');
if ($transactionId === null) {
$this->transactionService->createPendingTransaction($salesChannelContext);
$this->pluginTransactionService->createPendingTransaction($salesChannelContext);
}
$redirectUrl = $this->transactionService->create($transaction, $salesChannelContext);
$redirectUrl = $this->pluginTransactionService->create($transaction, $salesChannelContext);
}
return new RedirectResponse($redirectUrl);
} catch (\Exception $e) {
unset($_SESSION['transactionId']);
} catch (\Throwable $e) {
$request->getSession()->remove('transactionId');
$errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage();
$this->logger->critical($errorMessage);
throw new \Exception($transaction->getOrderTransaction()->getId() . ': ' . $errorMessage);
throw PaymentException::customerCanceled($transaction->getOrderTransaction()->getId(), $errorMessage);
}
}
/**
* The finalize function will be called when the user is redirected back to shop from the payment gateway.
*
* Throw a @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Context $context
* @throws \VRPayment\Sdk\ApiException
* @throws \VRPayment\Sdk\Http\ConnectionException
* @throws \VRPayment\Sdk\VersioningException
* @see AsyncPaymentFinalizeException exception if an error ocurres while calling an external payment API
* Throw a @see CustomerCanceledAsyncPaymentException exception if the customer canceled the payment process on
* payment provider page
*
* @throws \Exception when the payment was canceled by the customer
*/
public function finalize(
AsyncPaymentTransactionStruct $transaction,
Request $request,
SalesChannelContext $salesChannelContext
Request $request,
PaymentTransactionStruct $transaction,
Context $context
): void
{
if ($transaction->getOrder()->getAmountTotal() > 0) {
$transactionEntity = $this->transactionService->getByOrderId(
$transaction->getOrder()->getId(),
$salesChannelContext->getContext()
$orderTransactionId = $transaction->getOrderTransactionId();
$orderTransaction = $this->orderTransactionRepository->search(
(new Criteria([$orderTransactionId]))
->addAssociation('order'), $context
)->getEntities()->first();
if ($orderTransaction->getOrder()->getAmountTotal() > 0) {
$transactionEntity = $this->pluginTransactionService->getByOrderId(
$orderTransaction->getOrder()->getId(),
$context
);
$vRPaymentTransaction = $this->transactionService->read(
$vRPaymentTransaction = $this->pluginTransactionService->read(
$transactionEntity->getTransactionId(),
$salesChannelContext->getSalesChannel()->getId()
$transactionEntity->getSalesChannelId()
);
if (in_array($vRPaymentTransaction->getState(), [TransactionState::FAILED])) {
$errorMessage = strtr('Customer canceled payment for :orderId on SalesChannel :salesChannelName', [
':orderId' => $transaction->getOrder()->getId(),
':salesChannelName' => $salesChannelContext->getSalesChannel()->getName(),
':orderId' => $orderTransaction->getOrder()->getId(),
':salesChannelName' => $transactionEntity->getSalesChannelId(),
]);
unset($_SESSION['transactionId']);
$request->getSession()->remove('transactionId');
$this->logger->info($errorMessage);
throw PaymentException::customerCanceled($transaction->getOrderTransaction()->getId(), $errorMessage);
}
} else {
$this->orderTransactionStateHandler->paid($transaction->getOrderTransaction()->getId(), $salesChannelContext->getContext());
$this->orderTransactionStateHandler->paid($orderTransaction->getId(), $context);
}
$token = $request->getSession()->get('sw-context-token');
if ($token) {
$salesChannelId = $transactionEntity->getSalesChannelId();
$parameters = new SalesChannelContextServiceParameters($salesChannelId, $token, originalContext: $context);
$salesChannelContext = $this->salesChannelContextService->get($parameters);
$salesChannelContext->getContext()->addState('do-cart-delete');
$this->logger->info('Clearing cart with token: ' . $token);
$this->cartPersister->delete($salesChannelContext->getToken(), $salesChannelContext);
}
}
/**
* {@inheritDoc}
*/
public function supports(
PaymentHandlerType $type,
string $paymentMethodId,
Context $context
): bool {
// Both PaymentHandlerType::RECURRING and PaymentHandlerType::REFUND are supported
//TODO: check that the payment method really supports recurring.
// In order to do that, we need to get this information in when synching the payment methods.
// The payment methods in the portal are managed by their Connectors. The Connectors need
// to support the recurrin and the refunding. These values are 1453357059666L and 1453351315899L for
// tokenization and refunding respectively.
return true;
}
public function recurring(
PaymentTransactionStruct $transaction,
Context $context
): void {
if ($this->subscriptionRepository === null || !class_exists(\Shopware\Commercial\Subscription\Entity\Subscription\SubscriptionEntity::class)) {
throw PaymentException::paymentTypeUnsupported(
$transaction->getOrderTransactionId(),
'Shopware Commercial plugin with Subscription feature is not installed or active. Recurring payments cannot be processed.'
);
}
if ($transaction->isRecurring() === false) {
//TODO: Provide payment-method-id instead of order-transaction-id
throw PaymentException::paymentTypeUnsupported($transaction->getOrderTransaction()->getId(), PaymentHandlerType::RECURRING);
}
$recurringData = $transaction->getRecurring();
$newTransactionId = $transaction->getOrderTransactionId();
if ($recurringData === null) {
throw PaymentException::recurringInterrupted($newTransactionId, 'Recurring payment data is missing from the transaction struct.');
}
try {
// Get information about the subscription
$subscriptionId = $recurringData->getSubscriptionId();
$criteria = new Criteria([$subscriptionId]);
$criteria->addAssociation('orders.transactions.stateMachineState');
/** @var SubscriptionEntity|null $subscription */
$subscription = $this->subscriptionRepository->search($criteria, $context)->get($subscriptionId);
if ($subscription === null) {
throw PaymentException::recurringInterrupted($newTransactionId, sprintf('Subscription with ID "%s" could not be found.', $subscriptionId));
}
// Find the original order and transaction
$orders = $subscription->getOrders();
if ($orders === null || $orders->count() === 0) {
throw PaymentException::recurringInterrupted($newTransactionId, 'No orders found associated with the subscription.');
}
$orders->sort(fn (OrderEntity $a, OrderEntity $b) => $a->getCreatedAt() <=> $b->getCreatedAt());
/** @var OrderEntity|null $originalOrder */
$originalOrder = $orders->first();
$originalTransactions = $originalOrder->getTransactions();
if ($originalTransactions === null) {
throw PaymentException::recurringInterrupted($newTransactionId, 'No transactions found on the original order.');
}
/** @var OrderTransactionEntity|null $originalTransaction */
$originalTransaction = $originalTransactions->filter(
fn (OrderTransactionEntity $t) => $t->getStateMachineState()?->getTechnicalName() === OrderTransactionStates::STATE_PAID
)->first();
if ($originalTransaction === null) {
throw PaymentException::recurringInterrupted($newTransactionId, 'A successful, paid transaction could not be found on the original order to retrieve payment details.');
}
$newOrderTransaction = $this->orderTransactionRepository->search(
(new Criteria([$newTransactionId]))
->addAssociation('order'), $context
)->getEntities()->first();
$orderNumber = $newOrderTransaction->getOrder()->getOrderNumber();
// Access the custom fields for getting the original transaction details
$customFields = $originalTransaction->getCustomFields();
// The tokenReference is not really needed because it's also stored in the original transaction
$tokenReference = $customFields[TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TOKEN] ?? null;
$spaceId = (string) $customFields[TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID] ?? null;
$sdkTransactionId = $customFields[TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID] ?? null;
if ($sdkTransactionId === null || $spaceId === null) {
throw PaymentException::recurringInterrupted($newTransactionId, 'Required original transaction ID and spaceId is missing from order transaction custom fields.');
}
/** @var \VRPayment\Sdk\Model\Transaction $originalSdkTransaction */
$originalSdkTransaction = $this->pluginTransactionService->read($sdkTransactionId, "");
//TODO: Consider moving this logic to its own function for improved readability
$sdkTransactionCreate = new \VRPayment\Sdk\Model\TransactionCreate;
// Build the new transaction based on the original transaction
$sdkTransactionCreate->setCurrency($originalSdkTransaction->getCurrency());
$sdkTransactionCreate->setBillingAddress($this->addressCreateFromSdk($originalSdkTransaction->getBillingAddress()));
$sdkTransactionCreate->setShippingAddress($this->addressCreateFromSdk($originalSdkTransaction->getShippingAddress()));
$sdkTransactionCreate->setShippingMethod($originalSdkTransaction->getShippingMethod());
$sdkTransactionCreate->setCustomerEmailAddress($originalSdkTransaction->getCustomerEmailAddress());
$sdkTransactionCreate->setCustomerId($originalSdkTransaction->getCustomerId());
$sdkTransactionCreate->setLanguage($originalSdkTransaction->getLanguage());
// Get the merchant reference from the new Order, not the original one
$sdkTransactionCreate->setMerchantReference($orderNumber);
$sdkTransactionCreate->setInvoiceMerchantReference($originalSdkTransaction->getInvoiceMerchantReference());
$lineItems = $originalSdkTransaction->getLineItems();
$lineItemsCreate = [];
foreach ($lineItems as $lineItem) {
$lineItemsCreate[] = $this->lineItemCreateFromSdk($lineItem);
}
if (count($lineItemsCreate) > 0) {
$sdkTransactionCreate->setLineItems($lineItemsCreate);
}
$sdkTransactionCreate->setSuccessUrl($originalSdkTransaction->getSuccessUrl());
$sdkTransactionCreate->setToken($originalSdkTransaction->getToken());
$sdkTransactionCreate->setTokenizationMode($originalSdkTransaction->getTokenizationMode());
$sdkTransactionCreate->setMetaData($originalSdkTransaction->getMetaData());
// Create the new recurring transaction
$newSdkTransaction = $this->pluginTransactionService->createRecurringTransaction($sdkTransactionCreate, $spaceId);
// Set the new state for the new order transaction
if (in_array($newSdkTransaction->getState(), [TransactionState::AUTHORIZED, TransactionState::COMPLETED, TransactionState::CONFIRMED, TransactionState::FULFILL])) {
$this->orderTransactionStateHandler->paid($newTransactionId, $context);
} elseif (in_array($newSdkTransaction->getState(), [TransactionState::DECLINE, TransactionState::FAILED, TransactionState::VOIDED])) {
$this->orderTransactionStateHandler->fail($newTransactionId, $context);
} elseif (in_array($newSdkTransaction->getState(), [TransactionState::PENDING, TransactionState::PROCESSING])) {
$this->orderTransactionStateHandler->process($newTransactionId, $context);
} else {
$this->orderTransactionStateHandler->reopen($newTransactionId, $context);
}
$data = [
'id' => $newTransactionId,
'customFields' => [
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID => $newSdkTransaction->getId(),
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID => $spaceId,
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TOKEN => $tokenReference,
],
];
// Update the new order transaction with the new transaction details
$this->orderTransactionRepository->update([$data], $context);
$this->pluginTransactionService->upsert($newSdkTransaction, $context);
}
catch (\Throwable $e) {
$errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage();
$this->logger->critical($errorMessage);
throw PaymentException::recurringInterrupted($transaction->getOrderTransactionId(), $errorMessage);
}
}
/**
* Creates a new AddressCreate instance from the given SDK Address model.
*
* @param \VRPayment\Sdk\Model\Address $address The address model from the SDK.
* @return \VRPayment\Sdk\Model\AddressCreate The newly created AddressCreate instance.
*/
private function addressCreateFromSdk(\VRPayment\Sdk\Model\Address $address): \VRPayment\Sdk\Model\AddressCreate {
$addressCreate = new \VRPayment\Sdk\Model\AddressCreate;
$addressCreate->setCity($address->getCity());
$addressCreate->setCommercialRegisterNumber($address->getCommercialRegisterNumber());
$addressCreate->setCountry($address->getCountry());
$addressCreate->setDateOfBirth($address->getDateOfBirth());
$addressCreate->setDependentLocality($address->getDependentLocality());
$addressCreate->setEmailAddress($address->getEmailAddress());
$addressCreate->setFamilyName($address->getFamilyName());
$addressCreate->setGender($address->getGender());
$addressCreate->setGivenName($address->getGivenName());
$addressCreate->setMobilePhoneNumber($address->getMobilePhoneNumber());
$addressCreate->setOrganizationName($address->getOrganizationName());
$addressCreate->setPhoneNumber($address->getPhoneNumber());
$addressCreate->setPostalState($address->getPostalState());
$addressCreate->setPostcode($address->getPostcode());
$addressCreate->setSalesTaxNumber($address->getSalesTaxNumber());
$addressCreate->setSalutation($address->getSalutation());
$addressCreate->setSocialSecurityNumber($address->getSocialSecurityNumber());
$addressCreate->setSortingCode($address->getSortingCode());
$addressCreate->setStreet($address->getStreet());
return $addressCreate;
}
/**
* Creates a LineItemCreate object from a given SDK LineItem.
*
* This method takes a \VRPayment\Sdk\Model\LineItem instance and transforms it into a
* \VRPayment\Sdk\Model\LineItemCreate object, which can be used for further processing
* or integration with the VRPayment payment SDK.
*
* @param \VRPayment\Sdk\Model\LineItem $lineItem The line item from the SDK to convert.
* @return \VRPayment\Sdk\Model\LineItemCreate The created LineItemCreate object.
*/
private function lineItemCreateFromSdk(\VRPayment\Sdk\Model\LineItem $lineItem): \VRPayment\Sdk\Model\LineItemCreate
{
$lineItemCreate = new \VRPayment\Sdk\Model\LineItemCreate();
$lineItemCreate->setAmountIncludingTax($lineItem->getAmountIncludingTax());
$attributes = $lineItem->getAttributes();
$attributesCreate = [];
foreach ($attributes as $id => $attribute) {
$attributeCreate = new \VRPayment\Sdk\Model\LineItemAttributeCreate();
$attributeCreate->setLabel($attribute->getLabel());
$attributeCreate->setValue($attribute->getValue());
$attributesCreate[$id] = $attributeCreate;
}
if (count($attributesCreate) > 0) {
$lineItemCreate->setAttributes($attributesCreate);
}
$lineItemCreate->setDiscountIncludingTax($lineItem->getDiscountIncludingTax());
$lineItemCreate->setName($lineItem->getName());
$lineItemCreate->setQuantity($lineItem->getQuantity());
$lineItemCreate->setShippingRequired($lineItem->getShippingRequired());
$lineItemCreate->setSku($lineItem->getSku());
$taxes = $lineItem->getTaxes();
$taxesCreate = [];
foreach ($taxes as $tax) {
$taxCreate = new \VRPayment\Sdk\Model\TaxCreate();
$taxCreate->setRate($tax->getRate());
$taxCreate->setTitle($tax->getTitle());
$taxesCreate[] = $taxCreate;
}
if (count($taxesCreate) > 0) {
$lineItemCreate->setTaxes($taxesCreate);
}
$lineItemCreate->setType($lineItem->getType());
$lineItemCreate->setUniqueId($lineItem->getUniqueId());
return $lineItemCreate;
}
}
@@ -0,0 +1,116 @@
<?php declare(strict_types=1);
namespace VRPaymentPayment\Core\Checkout\Subscription\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Shopware\Commercial\Subscription\Checkout\Order\Generation\GenerateSubscriptionOrder;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Adapter\Console\ShopwareStyle;
use Shopware\Core\Framework\Context;
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\Uuid\Uuid;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Messenger\MessageBusInterface;
#[AsCommand(
name: 'vrpayment:subscription:generate',
description: 'Manually dispatches a message to generate a recurring subscription order.',
)]
class GenerateSubscriptionOrderCommand extends Command
{
public function __construct(
private readonly MessageBusInterface $bus,
private readonly ?EntityRepository $subscriptionRepository
) {
parent::__construct();
}
protected function configure(): void
{
$this->addArgument('subscriptionIdentifier', InputArgument::REQUIRED, 'The ID or Number of the subscription to process.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new ShopwareStyle($input, $output);
if ($this->subscriptionRepository === null || !class_exists(\Shopware\Commercial\Subscription\Entity\Subscription\SubscriptionEntity::class)) {
$io->error('Subscription functionality is not available in this Shopware instance. Please ensure the Subscription plugin is installed and enabled.');
return self::FAILURE;
}
$identifier = $input->getArgument('subscriptionIdentifier');
if (!is_string($identifier)) {
$io->error('Invalid Subscription ID provided.');
return self::FAILURE;
}
$subscriptionId = $this->findSubscriptionId($identifier, $io);
if ($subscriptionId === null) {
// Error message is already printed in findSubscriptionId
return self::FAILURE;
}
$io->text(sprintf('Forcing next schedule for subscription ID: %s', $subscriptionId));
$this->forceNextSchedule($subscriptionId);
$io->title('Subscription Order Generation');
$io->text(sprintf('Dispatching GenerateSubscriptionOrder message for subscription ID: %s', $subscriptionId));
$this->bus->dispatch(new GenerateSubscriptionOrder($subscriptionId));
$io->success('Message dispatched successfully!');
$io->note('Ensure a message consumer is running to process the queue: "bin/console messenger:consume async"');
return self::SUCCESS;
}
/**
* Set the next schedule date to the current time, so it will be processed immediately.
*
* @param string $subscriptionId
* @return void
*/
private function forceNextSchedule(string $subscriptionId): void
{
$context = Context::createDefaultContext();
$this->subscriptionRepository->update([
[
'id' => $subscriptionId,
'nextSchedule' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT),
]
], $context);
}
private function findSubscriptionId(string $identifier, ShopwareStyle $io): ?string
{
$context = Context::createDefaultContext();
if (Uuid::isValid($identifier)) {
// Check if a subscription with this ID actually exists
$result = $this->subscriptionRepository->searchIds(new Criteria([$identifier]), $context);
if ($result->firstId()) {
return $identifier;
}
$io->error(sprintf('No subscription found with ID "%s".', $identifier));
return null;
}
// If not a UUID, assume it's a subscription number
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('subscriptionNumber', $identifier));
$result = $this->subscriptionRepository->searchIds($criteria, $context);
if ($result->firstId() === null) {
$io->error(sprintf('No subscription found with number "%s".', $identifier));
return null;
}
return $result->firstId();
}
}
@@ -100,6 +100,7 @@ class CheckoutSubscriber implements EventSubscriberInterface
{
return [
CheckoutConfirmPageLoadedEvent::class => ['onConfirmPageLoaded', 1],
"subscription." . CheckoutConfirmPageLoadedEvent::class => ['onConfirmPageLoaded', 1],
MailBeforeValidateEvent::class => ['onMailBeforeValidate', 1],
];
}
+1 -1
View File
@@ -25,7 +25,7 @@ class Analytics {
self::SHOP_SYSTEM => 'shopware',
self::SHOP_SYSTEM_VERSION => '6',
self::SHOP_SYSTEM_AND_VERSION => 'shopware-6',
self::PLUGIN_SYSTEM_VERSION => '6.1.15',
self::PLUGIN_SYSTEM_VERSION => '7.1.0',
];
}
+161 -54
View File
@@ -8,10 +8,14 @@ use Shopware\Core\{Checkout\Cart\Tax\Struct\CalculatedTaxCollection,
Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity,
Checkout\Customer\CustomerEntity,
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity,
Checkout\Payment\Cart\AsyncPaymentTransactionStruct,
Checkout\Order\OrderEntity,
Checkout\Payment\Cart\PaymentTransactionStruct,
Framework\DataAbstractionLayer\Search\Criteria,
System\SalesChannel\SalesChannelContext
};
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use VRPayment\Sdk\{Model\AddressCreate,
@@ -36,6 +40,10 @@ use VRPaymentPayment\Core\{Api\PaymentMethodConfiguration\Entity\PaymentMethodCo
Util\Payload\CustomProducts\CustomProductsLineItemTypes
};
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Core\Framework\Context;
use Shopware\Core\System\Tax\TaxEntity;
/**
* Class TransactionPayload
*
@@ -48,6 +56,7 @@ class TransactionPayload extends AbstractPayload
public const ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID = 'vrpayment_space_id';
public const ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID = 'vrpayment_transaction_id';
public const ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TOKEN = 'vrpayment_token';
public const VRPAYMENT_METADATA_SALES_CHANNEL_ID = 'salesChannelId';
public const VRPAYMENT_METADATA_ORDER_ID = 'orderId';
@@ -61,7 +70,7 @@ class TransactionPayload extends AbstractPayload
protected $salesChannelContext;
/**
* @var \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct
* @var \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct
*/
protected $transaction;
@@ -85,6 +94,10 @@ class TransactionPayload extends AbstractPayload
*/
protected $translator;
protected EntityRepository $orderTransactionRepository;
protected OrderEntity $order;
/**
* TransactionPayload constructor.
*
@@ -92,14 +105,14 @@ class TransactionPayload extends AbstractPayload
* @param \VRPaymentPayment\Core\Util\LocaleCodeProvider $localeCodeProvider
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
* @param \VRPaymentPayment\Core\Settings\Struct\Settings $settings
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
*/
public function __construct(
ContainerInterface $container,
LocaleCodeProvider $localeCodeProvider,
SalesChannelContext $salesChannelContext,
Settings $settings,
AsyncPaymentTransactionStruct $transaction
PaymentTransactionStruct $transaction
)
{
$this->localeCodeProvider = $localeCodeProvider;
@@ -108,6 +121,23 @@ class TransactionPayload extends AbstractPayload
$this->transaction = $transaction;
$this->container = $container;
$this->translator = $this->container->get('translator');
$this->orderTransactionRepository = $this->container->get('order_transaction.repository');
$criteria = (new Criteria());
$criteria->addFilter(new EqualsFilter('id', $this->transaction->getOrderTransactionId()));
$orders = $this->orderTransactionRepository->search($criteria, $this->salesChannelContext->getContext())->getEntities();
$orderId = $orders->first()->getOrderId();
$criteria = new Criteria([$orderId]);
$criteria
->addAssociation('lineItems')
->addAssociation('orderCustomer')
->addAssociation('transactions')
->addAssociation('currency')
;
$this->order = $this->container->get('order.repository')->search($criteria, $this->salesChannelContext->getContext())->getEntities()->first();
}
/**
@@ -118,13 +148,21 @@ class TransactionPayload extends AbstractPayload
*/
public function get(int $version): TransactionPending
{
$customer = $this->salesChannelContext->getCustomer();
$customerId = $this->order->getOrderCustomer()->getCustomerId();
$criteria = new Criteria([$customerId]);
$criteria->addAssociation('activeBillingAddress')
->addAssociation('activeShippingAddress')
->addAssociation('activeShippingAddress')
->addAssociation('defaultBillingAddress')
->addAssociation('defaultShippingAddress')
->addAssociation('salutation');
$customer = $this->container->get('customer.repository')->search($criteria, $this->salesChannelContext->getContext())->getEntities()->first();
$lineItems = $this->getLineItems();
$billingAddress = $this->getAddressPayload($customer, $customer->getActiveBillingAddress());
$shippingAddress = $this->getAddressPayload($customer, $customer->getActiveShippingAddress(), false);
$customerId = null;
$customerName = null;
if ($customer->getGuest() === false) {
@@ -137,14 +175,14 @@ class TransactionPayload extends AbstractPayload
}
$transactionData = [
'currency' => $this->salesChannelContext->getCurrency()->getIsoCode(),
'customer_email_address' => $billingAddress->getEmailAddress(),
'currency' => $this->order->getCurrency()->getIsoCode(),
'customer_email_address' => $customer->getEmail(),
'customer_id' => $customerId,
'language' => $this->localeCodeProvider->getLocaleCodeFromContext($this->salesChannelContext->getContext()) ?? null,
'merchant_reference' => $this->fixLength($this->transaction->getOrder()->getOrderNumber(), 100),
'merchant_reference' => $this->fixLength($this->order->getOrderNumber(), 100),
'meta_data' => [
self::VRPAYMENT_METADATA_ORDER_ID => $this->transaction->getOrder()->getId(),
self::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID => $this->transaction->getOrderTransaction()->getId(),
self::VRPAYMENT_METADATA_ORDER_ID => $this->order->getId(),
self::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID => $this->order->getTransactions()->first()->getId(),
self::VRPAYMENT_METADATA_SALES_CHANNEL_ID => $this->salesChannelContext->getSalesChannel()->getId(),
self::VRPAYMENT_METADATA_CUSTOMER_NAME => $customerName,
],
@@ -161,8 +199,8 @@ class TransactionPayload extends AbstractPayload
$transactionData['meta_data']['additionalAddress2'] = $additionalAddress2;
}
if (!empty($this->transaction->getOrder()->getCustomerComment())) {
$transactionData['meta_data']['customer_comment'] = $this->transaction->getOrder()->getCustomerComment();
if (!empty($this->order->getCustomerComment())) {
$transactionData['meta_data']['customer_comment'] = $this->order->getCustomerComment();
}
$vatIds = $customer->getVatIds();
@@ -198,7 +236,7 @@ class TransactionPayload extends AbstractPayload
$transactionPayload->setAllowedPaymentMethodConfigurations([$paymentConfiguration->getPaymentMethodConfigurationId()]);
$successUrl = $this->transaction->getReturnUrl() . '&status=paid';
$failedUrl = $this->getFailUrl($this->transaction->getOrder()->getId()) . '&status=fail';
$failedUrl = $this->getFailUrl($this->order->getId()) . '&status=fail';
$transactionPayload->setSuccessUrl($successUrl)
->setFailedUrl($failedUrl);
@@ -219,7 +257,7 @@ class TransactionPayload extends AbstractPayload
protected function getLineItems(): array
{
$lineItems = [];
$items = $this->transaction->getOrder()->getLineItems();
$items = $this->order->getLineItems() ?? [];
foreach ($items as $shopLineItem) {
if ($this->shouldSkipLineItem($shopLineItem)) {
@@ -307,36 +345,99 @@ class TransactionPayload extends AbstractPayload
protected function addDiscountLineItem($discount, array &$lineItems): void
{
$calculatedPrice = $discount->getPrice();
$calculatedTaxesCollection = $calculatedPrice->getCalculatedTaxes();
$discountName = $discount->getLabel() ?? 'Unnamed';
$definition = $discount->getPriceDefinition();
foreach ($calculatedTaxesCollection as $calculatedTax) {
$rate = $calculatedTax->getTaxRate();
$lineItem = new LineItemCreate();
$amount = $this->calculateDiscountAmount($calculatedTax);
if ($this->order->getTaxStatus() === 'net' || $definition instanceof \Shopware\Core\Checkout\Cart\Price\Struct\AbsolutePriceDefinition) {
$calculatedTaxesCollection = $calculatedPrice->getCalculatedTaxes();
foreach ($calculatedTaxesCollection as $calculatedTax) {
$rate = $calculatedTax->getTaxRate();
$amount = $this->calculateDiscountAmount($calculatedTax);
$discountName = $discount->getLabel();
$lineItem->setAmountIncludingTax($amount)
->setName(sprintf('DISCOUNT: %s (%s%% tax)', $discount->getLabel(), $rate))
->setQuantity(1)
->setShippingRequired(false)
->setSku('sku-discount-' . $rate . '-' . $discountName, 200)
->setType(LineItemType::DISCOUNT)
->setUniqueId('coupon-sku-discount-' . $rate . '-' . $rate . '-' . $discountName);
$lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate);
}
} else {
$taxRules = $calculatedPrice->getTaxRules();
$taxRate = new TaxCreate(['title' => 'Discount Tax: ' . $rate, 'rate' => $rate]);
$lineItem->setTaxes([$taxRate]);
$lineItems[] = $lineItem;
if ($taxRules && $taxRules->count() > 0) {
foreach ($taxRules as $taxRule) {
$rate = $taxRule->getTaxRate();
$amount = $calculatedPrice->getTotalPrice();
$lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate);
}
} else {
$rate = $this->getDefaultTaxRate();
$amount = $calculatedPrice->getTotalPrice();
$lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate);
}
}
}
/**
* @param string $discountName
* @param float $amount
* @param float $rate
* @return LineItemCreate
*/
private function createDiscountLineItem(string $discountName, float $amount, float $rate): LineItemCreate
{
$lineItem = new LineItemCreate();
$discountSkuName = 'sku-discount-' . $rate . '-' . $discountName;
$discountTitle = sprintf('DISCOUNT: %s (%s%% tax)', $discountName, $rate);
if ($this->order->getTaxStatus() === 'tax-free') {
$discountSkuName = 'sku-discount-' . $discountName;
$discountTitle = sprintf('DISCOUNT: %s', $discountName);
}
$lineItem->setAmountIncludingTax($amount)
->setName($discountTitle)
->setQuantity(1)
->setShippingRequired(false)
->setSku($discountSkuName, 200)
->setType(LineItemType::DISCOUNT)
->setUniqueId('coupon-' . $discountSkuName);
$taxRate = new TaxCreate([
'title' => 'Discount Tax: ' . $rate,
'rate' => $rate,
]);
if ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes([$taxRate]);
}
return $lineItem;
}
/**
* @return float
*/
private function getDefaultTaxRate(): float
{
/** @var SystemConfigService $systemConfigService */
$systemConfigService = $this->container->get(SystemConfigService::class);
$taxId = $systemConfigService->get('core.tax.defaultTaxRate');
if (!$taxId || !is_string($taxId)) {
return 21.0;
}
$criteria = new Criteria([$taxId]);
/** @var TaxRepository $taxRepository */
$taxRepository = $this->container->get('tax.repository');
$tax = $taxRepository->search($criteria, Context::createDefaultContext())->get($taxId);
return $tax instanceof TaxEntity ? $tax->getTaxRate() : 21.0;
}
/**
* Calculate discount amount including tax if necessary.
*/
protected function calculateDiscountAmount($calculatedTax): float
{
$amount = self::round($calculatedTax->getPrice());
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $calculatedTax->getTax());
}
return $amount;
@@ -357,9 +458,7 @@ class TransactionPayload extends AbstractPayload
*/
protected function addOptionalLineItems(array &$lineItems): void
{
$shippingCosts = $this->transaction->getOrder()->getShippingCosts();
if ($shippingCosts && $this->transaction->getOrder()->getShippingTotal() > 0) {
if ($this->order->getShippingCosts() && $this->order->getShippingTotal() > 0) {
if ($shippingLineItem = $this->getShippingLineItem()) {
$lineItems[] = $shippingLineItem;
}
@@ -381,7 +480,7 @@ class TransactionPayload extends AbstractPayload
protected function getCustomProductOptionLabel(string $lineItemParentId): string
{
$label = '';
foreach ($this->transaction->getOrder()->getLineItems() as $shopLineItem) {
foreach ($this->order->getLineItems() as $shopLineItem) {
if ($shopLineItem->getParentId() === $lineItemParentId && $shopLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT) {
$label = $shopLineItem->getLabel();
break;
@@ -406,10 +505,11 @@ class TransactionPayload extends AbstractPayload
$sku = $payLoad['productNumber'];
}
$sku = $this->fixLength($sku, 200);
$amount = $shopLineItem->getTotalPrice() ? self::round($shopLineItem->getTotalPrice()) : 0;
//include Tax Excluded for Net Tax display customer group
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $shopLineItem->getPrice()->getCalculatedTaxes()->getAmount());
}
@@ -445,7 +545,9 @@ class TransactionPayload extends AbstractPayload
}
if (!empty($taxes)) {
$lineItem->setTaxes($taxes);
if ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes($taxes);
}
}
if ($shopLineItem->getTotalPrice() >= 0) {
@@ -521,31 +623,34 @@ class TransactionPayload extends AbstractPayload
{
try {
$amount = $this->transaction->getOrder()->getShippingTotal();
$amount = $this->order->getShippingTotal();
$amount = self::round($amount);
if ($amount > 0) {
$shippingName = $this->salesChannelContext->getShippingMethod()->getName() ?? $this->translator->trans('vrpayment.payload.shipping.name');
$taxes = $this->getTaxes(
$this->transaction->getOrder()->getShippingCosts()->getCalculatedTaxes(),
$this->order->getShippingCosts()->getCalculatedTaxes(),
$shippingName
);
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
$amount = self::round($amount + $this->transaction->getOrder()->getShippingCosts()->getCalculatedTaxes()->getAmount());
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $this->order->getShippingCosts()->getCalculatedTaxes()->getAmount());
}
$lineItem = (new LineItemCreate())
->setAmountIncludingTax($amount)
->setName($this->fixLength($shippingName . ' ' . $this->translator->trans('vrpayment.payload.shipping.lineItem'), 150))
->setQuantity($this->transaction->getOrder()->getShippingCosts()->getQuantity() ?? 1)
->setTaxes($taxes)
->setQuantity($this->order->getShippingCosts()->getQuantity() ?? 1)
->setSku($this->fixLength($shippingName . '-Shipping', 200))
/** @noinspection PhpParamsInspection */
->setType(LineItemType::SHIPPING)
->setUniqueId($this->fixLength($shippingName . '-Shipping', 200));
if ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes($taxes);
}
if (!$lineItem->valid()) {
$this->logger->critical('Shipping LineItem payload invalid:', $lineItem->listInvalidProperties());
throw new InvalidPayloadException('Shipping LineItem payload invalid:' . json_encode($lineItem->listInvalidProperties()));
@@ -566,15 +671,15 @@ class TransactionPayload extends AbstractPayload
protected function getMultipleShippingLineItems(): array
{
try {
if ($this->transaction->getOrder()->getShippingTotal() > 0) {
if ($this->order->getShippingTotal() > 0) {
$lineItems = [];
$shippingName = $this->salesChannelContext->getShippingMethod()->getName() ?? $this->translator->trans('vrpayment.payload.shipping.name');
$isFirst = true;
foreach ($this->transaction->getOrder()->getShippingCosts()->getCalculatedTaxes() as $taxItem) {
foreach ($this->order->getShippingCosts()->getCalculatedTaxes() as $taxItem) {
$amount = self::round($taxItem->getPrice());
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $taxItem->getTax());
}
$taxRate = $taxItem->getTaxRate();
@@ -586,12 +691,15 @@ class TransactionPayload extends AbstractPayload
$lineItem = (new LineItemCreate())
->setAmountIncludingTax($amount)
->setName($this->fixLength($name . ' ' . $this->translator->trans('vrpayment.payload.shipping.lineItem'), 150))
->setQuantity($this->transaction->getOrder()->getShippingCosts()->getQuantity() ?? 1)
->setTaxes([$tax])
->setQuantity($this->order->getShippingCosts()->getQuantity() ?? 1)
->setSku($this->fixLength($name . '-Shipping', 200))
->setType($isFirst ? LineItemType::SHIPPING : LineItemType::FEE) // First item as SHIPPING, rest as FEE
->setUniqueId($this->fixLength($name . '-Shipping', 200));
if ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes([$tax]);
}
if (!$lineItem->valid()) {
$this->logger->critical('Shipping LineItem payload invalid:', $lineItem->listInvalidProperties());
throw new InvalidPayloadException('Shipping LineItem payload invalid:' . json_encode($lineItem->listInvalidProperties()));
@@ -625,9 +733,8 @@ class TransactionPayload extends AbstractPayload
$lineItemPriceTotal = array_sum(array_map(static fn(LineItemCreate $li) => $li->getAmountIncludingTax(), $lineItems));
$this->logger->debug("LineItem price total before adjustment: $lineItemPriceTotal");
// Get shipping total including taxes from the order
$shippingCosts = $this->transaction->getOrder()->getShippingCosts();
$shippingCosts = $this->order->getShippingCosts();
$shippingTotal = $shippingCosts ? self::round($shippingCosts->getTotalPrice()) : 0.0;
// Add shipping to the line items total if it's not already included
@@ -636,13 +743,13 @@ class TransactionPayload extends AbstractPayload
$lineItemPriceTotal += $shippingTotal;
}
$adjustmentPrice = self::round($this->transaction->getOrder()->getAmountTotal() - $lineItemPriceTotal);
$adjustmentPrice = self::round($this->order->getAmountTotal() - $lineItemPriceTotal);
if (abs($adjustmentPrice) != 0) {
if ($this->settings->isLineItemConsistencyEnabled()) {
$error = strtr('LineItems total :lineItemTotal does not add up to order total :orderTotal', [
':lineItemTotal' => $lineItemPriceTotal,
':orderTotal' => $this->transaction->getOrder()->getAmountTotal(),
':orderTotal' => $this->order->getAmountTotal(),
]);
$this->logger->critical($error);
throw new \Exception($error);
@@ -4,21 +4,21 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_completion_amount %}
<sw-checkbox-field
<mt-checkbox
:label="$tc('vrpayment-order.captureAction.button.text')"
v-model:value="isCompletion">
</sw-checkbox-field>
v-model:checked="isCompletion">
</mt-checkbox>
{% endblock %}
{% block vrpayment_order_action_completion_confirm_button %}
<template #modal-footer>
<sw-button variant="primary"
<mt-button variant="primary"
@click="completion">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -4,23 +4,23 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_refund_amount_by_amount %}
<sw-number-field
<mt-number-field
:max="refundableAmount"
:min="0"
v-model:value="refundAmount"
v-model="refundAmount"
:label="$tc('vrpayment-order.refund.refundAmount.label')"
:suffix="currency">
</sw-number-field>
</mt-number-field>
{% endblock %}
{% block vrpayment_order_action_refund_confirm_button_by_amount %}
<template #modal-footer>
<sw-button variant="primary" @click="refundByAmount()">
<mt-button variant="primary" @click="refundByAmount()">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -70,9 +70,18 @@ Component.register('vrpayment-order-action-refund-by-amount', {
});
}).catch((errorResponse) => {
try {
var errorTitle;
var errorMessage;
if (errorResponse.response.data == 'refundExceedsAmount') {
errorTitle = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.title');
errorMessage = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.messageRefundAmountExceedsAvailableBalance');
} else {
errorTitle = errorResponse.response.data.errors[0].title;
errorMessage = errorResponse.response.data.errors[0].detail;
}
this.createNotificationError({
title: errorResponse.response.data.errors[0].title,
message: errorResponse.response.data.errors[0].detail,
title: errorTitle,
message: errorMessage,
autoClose: false
});
} catch (e) {
@@ -4,13 +4,13 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_refund_amount_partial %}
<sw-number-field
<mt-number-field
:max="this.$parent.$parent.itemRefundableAmount"
:min="0.00"
v-model:value="refundAmount"
v-model="refundAmount"
:label="$tc('vrpayment-order.refund.refundAmount.label')"
:suffix="currency">
</sw-number-field>
</mt-number-field>
<div>
{{ $tc('vrpayment-order.refundAction.maxAvailableAmountToRefund') }}:
@@ -20,12 +20,12 @@
{% block vrpayment_order_action_refund_confirm_button_partial %}
<template #modal-footer>
<sw-button variant="primary" @click="createPartialRefund(this.$parent.$parent.currentLineItem)">
<mt-button variant="primary" @click="createPartialRefund(this.$parent.$parent.currentLineItem)">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -47,7 +47,9 @@ Component.register('vrpayment-order-action-refund-partial', {
createdComponent() {
this.isLoading = false;
this.currency = this.transactionData.transactions[0].currency;
this.refundAmount = this.$parent.$parent.itemRefundableAmount;
if (!this.refundAmount) {
this.refundAmount = this.$parent.$parent.itemRefundableAmount;
}
},
createPartialRefund(itemUniqueId) {
@@ -5,12 +5,12 @@
{% block vrpayment_order_action_refund_confirm_button_selected %}
<template #modal-footer>
<sw-button variant="primary" @click="refundSelected()">
<mt-button variant="primary" @click="refundSelected()">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -5,12 +5,12 @@
{% block vrpayment_order_action_refund_amount %}
<sw-number-field
<mt-number-field
:max="this.$parent.$parent.itemRefundableQuantity"
:min="0"
v-model:value="refundQuantity"
v-model="refundQuantity"
:label="$tc('vrpayment-order.refund.refundQuantity.label')">
</sw-number-field>
</mt-number-field>
<div>
{{ $tc('vrpayment-order.refundAction.maxAvailableItemsToRefund') }}:
@@ -20,12 +20,12 @@
{% block vrpayment_order_action_refund_confirm_button %}
<template #modal-footer>
<sw-button variant="primary" @click="refund()">
<mt-button variant="primary" @click="refund()">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -4,21 +4,22 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_void_amount %}
<sw-checkbox-field
{# Review if this v-model:checked="isVoid" needs to change to checked #}
<mt-checkbox
:label="$tc('vrpayment-order.voidAction.confirm.message')"
v-model:value="isVoid">
</sw-checkbox-field>
v-model:checked="isVoid">
</mt-checkbox>
{% endblock %}
{% block vrpayment_order_action_void_confirm_button %}
<template #modal-footer>
<sw-button variant="primary"
<mt-button variant="primary"
@click="voidPayment">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -1,6 +1,7 @@
{% block sw_order_detail_content_tabs_general %}
{% parent %}
{# sw-tabs-item will dissappear. See: https://github.com/shopware/shopware/blob/trunk/UPGRADE-6.7.md#sw-tabs-is-removed #}
<sw-tabs-item v-if="isVRPaymentPayment"
:route="{ name: 'vrpayment.order.detail', params: { id: $route.params.id } }"
:title="$tc('vrpayment-order.header')">
@@ -3,7 +3,7 @@
margin-top: 40px;
}
.sw-order-detail-base .sw-card-view__content {
.sw-order-detail-base .mt-card-view__content {
overflow-x: visible;
overflow-y: visible;
}
@@ -1,61 +1,61 @@
{% block vrpayment_order_detail %}
<div class="vrpayment-order-detail">
<div v-if="!isLoading">
<sw-card :title="$tc('vrpayment-order.paymentDetails.cardTitle')">
<mt-card :title="$tc('vrpayment-order.paymentDetails.cardTitle')">
<template #grid>
{% block vrpayment_order_actions_section %}
<sw-card-section secondary slim>
<mt-card-section secondary slim>
{% block vrpayment_order_transaction_refunds_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'FULFILL' || refundableAmount <= 0"
@click="spawnModal('refundByAmount')">
{{ $tc('vrpayment-order.buttons.label.refund') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_completion_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'AUTHORIZED' || isLoading"
@click="spawnModal('completion')">
{{ $tc('vrpayment-order.buttons.label.completion') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_void_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'AUTHORIZED' || isLoading"
@click="spawnModal('void')">
{{ $tc('vrpayment-order.buttons.label.void') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_download_invoice_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'FULFILL'"
@click="downloadInvoice()">
{{ $tc('vrpayment-order.buttons.label.download-invoice') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_download_packing_slip_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'FULFILL'"
@click="downloadPackingSlip()">
{{ $tc('vrpayment-order.buttons.label.download-packing-slip') }}
</sw-button>
</mt-button>
{% endblock %}
</sw-card-section>
</mt-card-section>
{% endblock %}
</template>
</sw-card>
</mt-card>
{% block vrpayment_order_transaction_history_card %}
<sw-card :title="$tc('vrpayment-order.transactionHistory.cardTitle')">
<mt-card :title="$tc('vrpayment-order.transactionHistory.cardTitle')">
<template #grid>
{% block vrpayment_order_transaction_history_grid %}
@@ -78,10 +78,10 @@
{% endblock %}
</template>
</sw-card>
</mt-card>
{% endblock %}
{% block vrpayment_order_transaction_line_items_card %}
<sw-card :title="$tc('vrpayment-order.lineItem.cardTitle')">
<mt-card :title="$tc('vrpayment-order.lineItem.cardTitle')">
<template #grid>
{% block vrpayment_order_transaction_line_items_grid %}
@@ -131,10 +131,10 @@
</sw-data-grid>
{% endblock %}
</template>
</sw-card>
</mt-card>
{% endblock %}
{% block vrpayment_order_transaction_refunds_card %}
<sw-card :title="$tc('vrpayment-order.refund.cardTitle')" v-if="transactionData.refunds.length > 0">
<mt-card :title="$tc('vrpayment-order.refund.cardTitle')" v-if="transactionData.refunds.length > 0">
<template #grid>
{% block vrpayment_order_transaction_refunds_grid %}
@@ -147,7 +147,7 @@
{% endblock %}
</template>
</sw-card>
</mt-card>
{% endblock %}
{% block vrpayment_order_actions_modal_refund_partial %}
<vrpayment-order-action-refund-partial
@@ -195,6 +195,6 @@
</vrpayment-order-action-void>
{% endblock %}
</div>
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</div>
{% endblock %}
@@ -77,7 +77,11 @@
"successMessage": "Ihre Rückerstattung war erfolgreich",
"successTitle": "Erfolg",
"maxAvailableItemsToRefund": "Maximal Verfügbare Artikel zum Erstatten",
"maxAvailableAmountToRefund": "Maximal verfügbarer Erstattungsbetrag"
"maxAvailableAmountToRefund": "Maximal verfügbarer Erstattungsbetrag",
"refundExceedsTotalError": {
"title": "Fehler beim Erstellen der Rückerstattung.",
"messageRefundAmountExceedsAvailableBalance": "Der Rückerstattungsbetrag übersteigt das verfügbare Guthaben."
}
},
"transactionHistory": {
"cardTitle": "Einzelheiten",
@@ -78,7 +78,11 @@
"successMessage": "Your refund was successful.",
"successTitle": "Success",
"maxAvailableItemsToRefund": "Maximum available items to refund",
"maxAvailableAmountToRefund": "Maximum available amount to refund"
"maxAvailableAmountToRefund": "Maximum available amount to refund",
"refundExceedsTotalError": {
"title": "Error while creating the refund.",
"messageRefundAmountExceedsAvailableBalance": "Refund amount exceeds available balance."
}
},
"transactionHistory": {
"cardTitle": "Details",
@@ -77,7 +77,11 @@
"successMessage": "Votre remboursement a été effectué avec succès.",
"successTitle": "Succès",
"maxAvailableItemsToRefund": "Nombre maximum d'articles disponibles pour le remboursement",
"maxAvailableAmountToRefund": "Montant maximal disponible pour le remboursement"
"maxAvailableAmountToRefund": "Montant maximal disponible pour le remboursement",
"refundExceedsTotalError": {
"title": "Erreur lors de la création du remboursement.",
"messageRefundAmountExceedsAvailableBalance": "Le montant du remboursement dépasse le solde disponible."
}
},
"transactionHistory": {
"cardTitle": "Détails",
@@ -77,7 +77,11 @@
"successMessage": "Il tuo rimborso è andato a buon fine.",
"successTitle": "Successo",
"maxAvailableItemsToRefund": "Numero massimo di articoli disponibili da rimborsare",
"maxAvailableAmountToRefund": "Importo massimo disponibile per il rimborso"
"maxAvailableAmountToRefund": "Importo massimo disponibile per il rimborso",
"refundExceedsTotalError": {
"title": "Errore durante la creazione del rimborso.",
"messageRefundAmountExceedsAvailableBalance": "LL'importo del rimborso supera il saldo disponibile."
}
},
"transactionHistory": {
"cardTitle": "Dettagli",
@@ -1,4 +1,4 @@
<sw-card class="sw-card"
<mt-card class="mt-card"
:title="$tc('vrpayment-settings.settingForm.advancedOptions.cardTitle')">
<sw-container>
<div v-if="actualConfigData" class="vrpayment-settings-advanced-options-fields">
@@ -7,16 +7,16 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.advancedOptions.webhooksUpdateEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.advancedOptions.webhooksUpdateEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
@@ -25,19 +25,19 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.advancedOptions.paymentsUpdateEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.advancedOptions.paymentsUpdateEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
</div>
</sw-container>
</sw-card>
</mt-card>
@@ -1,6 +1,6 @@
{% block vrpayment_settings_content_card_channel_config_credentials %}
<sw-card
class="sw-card"
<mt-card
class="mt-card"
:title="$tc('vrpayment-settings.settingForm.credentials.cardTitle')"
v-if="actualConfigData"
>
@@ -17,17 +17,17 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_SPACE_ID]"
:customInheritationCheckFunction="checkNumberFieldInheritance">
<template #content="props">
<sw-number-field
<mt-number-field
:name="CONFIG_SPACE_ID"
:required="true"
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.credentials.spaceId.label')"
:helpText="$tc('vrpayment-settings.settingForm.credentials.spaceId.tooltipText')"
:disabled="props.isInherited || !acl.can('vrpayment.editor')"
:value="props.currentValue"
:model-value="props.currentValue"
:error="spaceIdErrorState"
@update:value="props.updateCurrentValue">
</sw-number-field>
@update:model-value="props.updateCurrentValue">
</mt-number-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -38,17 +38,17 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_USER_ID]"
:customInheritationCheckFunction="checkNumberFieldInheritance">
<template #content="props">
<sw-number-field
<mt-number-field
:name="CONFIG_USER_ID"
:required="true"
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.credentials.userId.label')"
:helpText="$tc('vrpayment-settings.settingForm.credentials.userId.tooltipText')"
:disabled="props.isInherited || !acl.can('vrpayment.editor')"
:value="props.currentValue"
:model-value="props.currentValue"
:error="userIdErrorState"
@update:value="props.updateCurrentValue">
</sw-number-field>
@update:model-value="props.updateCurrentValue">
</mt-number-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -59,7 +59,7 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_APPLICATION_KEY]"
:customInheritationCheckFunction="checkTextFieldInheritance">
<template #content="props">
<sw-password-field
<mt-password-field
:name="CONFIG_APPLICATION_KEY"
:required="true"
:passwordToggleAble="true"
@@ -67,26 +67,29 @@
:label="$tc('vrpayment-settings.settingForm.credentials.applicationKey.label')"
:helpText="$tc('vrpayment-settings.settingForm.credentials.applicationKey.tooltipText')"
:disabled="props.isInherited || !acl.can('vrpayment.editor')"
:value="props.currentValue"
:model-value="props.currentValue"
:error="applicationKeyErrorState"
@update:value="props.updateCurrentValue">
</sw-password-field>
@update:model-value="props.updateCurrentValue">
</mt-password-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
</div>
{% endblock %}
{% verbatim %}
<sw-container columns="1fr 1fr" gap="0px 30px">
<sw-button-process
<mt-button
variant="primary"
:isLoading="isTesting"
@click="emitCheckApiConnectionEvent">
{{ $tc('vrpayment-settings.settingForm.credentials.button.label') }}
</sw-button-process>
</mt-button>
</sw-container>
{% endverbatim %}
</sw-container>
{% endblock %}
</sw-card>
</mt-card>
{% endblock %}
@@ -1,5 +1,5 @@
{% block vrpayment_settings_content_card_channel_config_options %}
<sw-card class="sw-card"
<mt-card class="mt-card"
:title="$tc('vrpayment-settings.settingForm.options.cardTitle')">
{% block vrpayment_settings_content_card_channel_config_credentials_card_container %}
@@ -14,15 +14,15 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_SPACE_VIEW_ID]"
:customInheritationCheckFunction="checkNumberFieldInheritance">
<template #content="props">
<sw-number-field
<mt-number-field
:name="CONFIG_SPACE_VIEW_ID"
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.options.spaceViewId.label')"
:helpText="$tc('vrpayment-settings.settingForm.options.spaceViewId.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-number-field>
:model-value="props.currentValue"
@update:model-value="props.updateCurrentValue">
</mt-number-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -55,16 +55,16 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_LINE_ITEM_CONSISTENCY_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_LINE_ITEM_CONSISTENCY_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.options.lineItemConsistencyEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.options.lineItemConsistencyEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -75,16 +75,16 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_EMAIL_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_EMAIL_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.options.emailEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.options.emailEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -92,6 +92,6 @@
{% endblock %}
</sw-container>
{% endblock %}
</sw-card>
</mt-card>
{% endblock %}
@@ -1,5 +1,5 @@
{% block vrpayment_settings_icon %}
<span class="sw-icon icon--vrpayment-multicolor sw-icon--multicolor">
<span class="mt-icon icon--vrpayment-multicolor mt-icon--multicolor" style="width: 16px; height: 16px;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" contentScriptType="text/ecmascript" y="0px" zoomAndPan="magnify" style="enable-background:new 0 0 632.126 170.079;" contentStyleType="text/css" viewBox="0 0 632.126 170.079" preserveAspectRatio="xMidYMid meet" xml:space="preserve" version="1.1">
<style type="text/css" xml:space="preserve">
.st0{fill:none;}
@@ -1,4 +1,4 @@
<sw-card class="sw-card"
<mt-card class="mt-card"
:title="$tc('vrpayment-settings.settingForm.storefrontOptions.cardTitle')">
<sw-container>
<div v-if="actualConfigData" class="vrpayment-settings-storefront-options-fields">
@@ -7,19 +7,19 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.storefrontOptions.invoiceDownloadEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.storefrontOptions.invoiceDownloadEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
</div>
</sw-container>
</sw-card>
</mt-card>
@@ -5,7 +5,7 @@
<template #smart-bar-header>
<h2>
{{ $tc('sw-settings.index.title') }}
<sw-icon name="small-arrow-medium-right" small></sw-icon>
<mt-icon name="small-arrow-medium-right" size="16px"></mt-icon>
{{ $tc('vrpayment-settings.header') }}
</h2>
</template>
@@ -14,7 +14,7 @@
{% block vrpayment_settings_actions %}
<template #smart-bar-actions>
{% block vrpayment_settings_actions_save %}
<sw-button-process
<mt-button
v-model:value="isSaveSuccessful"
class="sw-settings-login-registration__save-action"
variant="primary"
@@ -22,7 +22,7 @@
:disabled="isLoading"
@click="onSave">
{{ $tc('vrpayment-settings.settingForm.save') }}
</sw-button-process>
</mt-button>
{% endblock %}
</template>
{% endblock %}
@@ -31,7 +31,7 @@
<template #content>
{% block vrpayment_settings_content_card %}
<sw-card-view>
<mt-card-view>
{% block vrpayment_settings_content_card_channel_config %}
<sw-sales-channel-config v-model:value="config"
@@ -42,18 +42,18 @@
<template #select="{ onInput, selectedSalesChannelId, salesChannel }">
{% block vrpayment_settings_content_card_channel_config_sales_channel_card %}
<sw-card title="Sales Channel Switch">
<mt-card title="Sales Channel Switch">
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_title %}
<sw-single-select
<sw-entity-single-select
v-model:value="selectedSalesChannelId"
labelProperty="translated.name"
valueProperty="id"
:mapInheritance="props"
:isLoading="isLoading"
:options="salesChannel"
entity="sales_channel"
@update:value="onInput">
</sw-single-select>
</sw-entity-single-select>
{% endblock %}
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer %}
<template #footer>
@@ -66,18 +66,19 @@
{% endblock %}
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container_button %}
<sw-button-process
<sw-button
variant="primary"
v-model:value="isSetDefaultPaymentSuccessful"
:isLoading="isSettingDefaultPaymentMethods"
@click="onSetPaymentMethodDefault">
{{ $tc('vrpayment-settings.salesChannelCard.button.label') }}
</sw-button-process>
</sw-button>
{% endblock %}
</sw-container>
{% endblock %}
</template>
{% endblock %}
</sw-card>
</mt-card>
{% endblock %}
</template>
{% endblock %}
@@ -134,12 +135,12 @@
{% endblock %}
{% block vrpayment_settings_content_card_loading %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
{% endblock %}
</sw-card-view>
</mt-card-view>
{% endblock %}
</template>
{% endblock %}
</sw-page>
{% endblock %}
{% endblock %}
@@ -65,18 +65,6 @@ Component.register('vrpayment-settings', {
};
},
created() {
// Registers a listener for the 'check-api-connection-event'.
// Triggered when this event is emitted.
this.$on('check-api-connection-event', this.onCheckApiConnection);
},
beforeDestroy() {
// Removes the listener for the 'check-api-connection-event'
// before the component is destroyed to prevent memory leaks.
this.$off('check-api-connection-event', this.onCheckApiConnection);
},
watch: {
config: {
handler(configData) {
@@ -5,13 +5,31 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- Commands -->
<service id="VRPaymentPayment\Core\Checkout\Subscription\Command\GenerateSubscriptionOrderCommand">
<argument type="service" id="Symfony\Component\Messenger\MessageBusInterface"/>
<argument type="service" id="subscription.repository" on-invalid="null" />
<tag name="console.command"/>
</service>
<!-- Services -->
<service id="VRPaymentPayment\Core\Checkout\PaymentHandler\VRPaymentPaymentHandler">
<argument type="service" id="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister"/>
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>
<argument type="service" id="Shopware\Core\System\SalesChannel\Context\SalesChannelContextService"/>
<argument type="service" id="order_transaction.repository"/>
<argument type="service" id="subscription.repository" on-invalid="null" />
<call method="setLogger">
<argument type="service" id="monolog.logger.vrpayment_payment"/>
</call>
<tag name="shopware.payment.method.async"/>
<tag name="shopware.payment.method"/>
</service>
<service id="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister"
decorates="Shopware\Core\Checkout\Cart\CartPersister"
decoration-inner-name="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister.inner">
<argument type="service" id="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister.inner"/>
</service>
</services>
@@ -19,9 +19,10 @@
<call method="setContainer">
<argument type="service" id="service_container"/>
</call>
<call method="setTwig">
<!-- Removed in 6.7 -->
<!-- <call method="setTwig">
<argument type="service" id="twig"/>
</call>
</call> -->
</service>
<!-- Subscribers -->
+2 -2
View File
@@ -29,7 +29,7 @@
payment_method_handler_status: 'input[name="vrpayment_payment_handler_validation_status"]',
payment_form_id: 'confirmOrderForm',
button_cancel_id: 'vrpaymentOrderCancel',
button_home_override: 'vrpaymentHomeLink',
// button_home_override: 'vrpaymentHomeLink',
loader_id: 'vrpaymentLoader',
checkout_url: null,
checkout_url_id: 'checkoutUrl',
@@ -46,7 +46,7 @@
this.cart_recreate_url = document.getElementById(this.cart_recreate_url_id).value;
document.getElementById(this.button_cancel_id).addEventListener('click', this.recreateCart, false);
document.getElementById(this.button_home_override).addEventListener('click', this.recreateCart, false);
// document.getElementById(this.button_home_override).addEventListener('click', this.recreateCart, false);
document.getElementById(this.payment_form_id).addEventListener('submit', this.submitPayment, false);
VRPaymentCheckout.getIframe();
@@ -1,15 +1,11 @@
{% sw_extends '@Storefront/storefront/page/checkout/_page.html.twig' %}
{% block base_header %}
{% sw_include '@VRPaymentPayment/storefront/page/checkout/order/vrpayment_header.html.twig' %}
{% endblock %}
{% block base_navigation %}{% endblock %}
{% block base_body_classes %}vrpayment-payment is-act-confirmpage{% endblock %}
{% block page_checkout_main_content %}
{% block page_checkout_pay %}
{% block page_checkout_confirm_header %}
<div id="vrpaymentOrderCancel"></div>
{% block page_checkout_pay %}
{% block page_checkout_confirm_header %}
<h1 class="confirm-main-header">
{{ "vrpayment.payHeader"|trans|sw_sanitize }}
</h1>
@@ -144,10 +140,6 @@
</div>
{% endblock %}
{% block base_footer %}
{% sw_include '@Storefront/storefront/layout/footer/footer-minimal.html.twig' %}
{% endblock %}
{% block base_body_script %}
{{ parent() }}
{% if page.extensions.vRPaymentData %}
@@ -1,53 +0,0 @@
{% sw_extends '@Storefront/storefront/layout/header/header-minimal.html.twig' %}
{% block layout_header_minimal_logo %}
<div class="col-6 col-md-4 header-minimal-logo">
{% block layout_header_logo_inner %}
<div class="header-logo-main">
{% block layout_header_logo_link %}
<a class="header-logo-main-link"
id="vrpaymentHomeLink"
href="{{ path('frontend.home.page') }}"
title="{{ "header.logoLink"|trans|striptags }}">
{% block layout_header_logo_image %}
<picture class="header-logo-picture">
{% block layout_header_logo_image_tablet %}
{% if theme_config('sw-logo-tablet') and theme_config('sw-logo-tablet') != theme_config('sw-logo-desktop') %}
<source srcset="{{ theme_config('sw-logo-tablet') |sw_encode_url }}"
media="(min-width: {{ theme_config('breakpoint.md') }}px) and (max-width: {{ theme_config('breakpoint.lg') - 1 }}px)">
{% endif %}
{% endblock %}
{% block layout_header_logo_image_mobile %}
{% if theme_config('sw-logo-mobile') and theme_config('sw-logo-mobile') != theme_config('sw-logo-desktop') %}
<source srcset="{{ theme_config('sw-logo-mobile') |sw_encode_url }}"
media="(max-width: {{ theme_config('breakpoint.md') - 1 }}px)">
{% endif %}
{% endblock %}
{% block layout_header_logo_image_default %}
{% if theme_config('sw-logo-desktop') %}
<img src="{{ theme_config('sw-logo-desktop') |sw_encode_url }}"
alt="{{ "header.logoLink"|trans|striptags }}"
class="img-fluid header-logo-main-img"/>
{% endif %}
{% endblock %}
</picture>
{% endblock %}
</a>
{% endblock %}
</div>
{% endblock %}
</div>
{% endblock %}
{% block layout_header_minimal_button %}
<div class="col-md-4 header-minimal-back-to-shop">
<button type="button"
id="vrpaymentOrderCancel"
class="btn btn-outline-primary header-minimal-back-to-shop-button"
title="{{ "checkout.finishButtonBackToShop"|trans|striptags }}">
{{ "checkout.finishButtonBackToShop"|trans|striptags }}
</button>
</div>
{% endblock %}
+10 -6
View File
@@ -3,6 +3,7 @@
namespace VRPaymentPayment;
use Shopware\Core\{
Framework\Feature,
Framework\Plugin,
Framework\Plugin\Context\ActivateContext,
Framework\Plugin\Context\DeactivateContext,
@@ -21,6 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
use Symfony\Component\DependencyInjection\Loader\GlobFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
// expect the vendor folder on Shopware store releases
if (file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
@@ -82,19 +84,21 @@ class VRPaymentPayment extends Plugin {
{
parent::build($container);
$locator = new FileLocator('Resources/config');
$confDir = \rtrim($this->getPath(), '/') . '/Resources/config';
$locator = new FileLocator($confDir);
$resolver = new LoaderResolver([
new YamlFileLoader($container, $locator),
new GlobFileLoader($container, $locator),
new DirectoryLoader($container, $locator),
new YamlFileLoader($container, $locator),
new XmlFileLoader($container, $locator),
new GlobFileLoader($container, $locator),
new DirectoryLoader($container, $locator),
]);
$configLoader = new DelegatingLoader($resolver);
$confDir = \rtrim($this->getPath(), '/') . '/Resources/config';
$configLoader->load($confDir . '/{packages}/*.yaml', 'glob');
$configLoader->load('services/core/checkout.xml');
}
public function enrichPrivileges(): array