mirror of
https://github.com/vr-payment/shopware-6.git
synced 2026-06-05 03:19:49 +00:00
Release 7.1.0
This commit is contained in:
@@ -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,
|
||||
@@ -195,6 +196,25 @@ class TransactionService
|
||||
return $redirectUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -391,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);
|
||||
@@ -598,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;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ 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\Order\Aggregate\OrderTransaction\OrderTransactionStates,
|
||||
Checkout\Payment\Cart\PaymentTransactionStruct,
|
||||
Checkout\Payment\Cart\PaymentHandler\AbstractPaymentHandler,
|
||||
Checkout\Payment\Cart\PaymentHandler\PaymentHandlerType,
|
||||
@@ -16,9 +18,11 @@ use Shopware\Core\{
|
||||
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\StateMachine\Aggregation\StateMachineState\StateMachineStateEntity,
|
||||
System\SalesChannel\Context\SalesChannelContextService,
|
||||
System\SalesChannel\Context\SalesChannelContextServiceParameters
|
||||
};
|
||||
@@ -33,7 +37,9 @@ use Symfony\Component\{
|
||||
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;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -50,9 +56,9 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
||||
private CustomCartPersister $cartPersister;
|
||||
|
||||
/**
|
||||
* @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService
|
||||
* @var \VRPaymentPayment\Core\Api\Transaction\Service\PluginTransactionService
|
||||
*/
|
||||
protected $transactionService;
|
||||
protected $pluginTransactionService;
|
||||
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
@@ -67,28 +73,26 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
||||
|
||||
protected EntityRepository $orderTransactionRepository;
|
||||
|
||||
protected ?EntityRepository $subscriptionRepository;
|
||||
|
||||
/**
|
||||
* VRPaymentPaymentHandler constructor.
|
||||
*
|
||||
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
|
||||
* @param \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler $orderTransactionStateHandler
|
||||
* @param SalesChannelContextService $salesChannelContextService
|
||||
* @param EntityRepository $orderTransactionRepository
|
||||
*/
|
||||
public function __construct(
|
||||
CustomCartPersister $cartPersister,
|
||||
TransactionService $transactionService,
|
||||
PluginTransactionService $pluginTransactionService,
|
||||
OrderTransactionStateHandler $orderTransactionStateHandler,
|
||||
SalesChannelContextService $salesChannelContextService,
|
||||
EntityRepository $orderTransactionRepository
|
||||
EntityRepository $orderTransactionRepository,
|
||||
?EntityRepository $subscriptionRepository,
|
||||
) {
|
||||
$this->cartPersister = $cartPersister;
|
||||
$this->transactionService = $transactionService;
|
||||
$this->pluginTransactionService = $pluginTransactionService;
|
||||
$this->orderTransactionStateHandler = $orderTransactionStateHandler;
|
||||
$this->salesChannelContextService = $salesChannelContextService;
|
||||
$this->orderTransactionRepository = $orderTransactionRepository;
|
||||
$this->subscriptionRepository = $subscriptionRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
*
|
||||
@@ -134,16 +138,15 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
||||
$redirectUrl = $transaction->getReturnUrl();
|
||||
|
||||
if ($orderTransaction->getOrder()->getAmountTotal() > 0) {
|
||||
$transactionId = $_SESSION['transactionId'] ?? null;
|
||||
$transactionId = $request->getSession()->get('transactionId');
|
||||
if ($transactionId === null) {
|
||||
$this->transactionService->createPendingTransaction($transaction, $salesChannelContext);
|
||||
$this->pluginTransactionService->createPendingTransaction($salesChannelContext);
|
||||
}
|
||||
$redirectUrl = $this->transactionService->create($transaction, $salesChannelContext);
|
||||
$redirectUrl = $this->pluginTransactionService->create($transaction, $salesChannelContext);
|
||||
}
|
||||
return new RedirectResponse($redirectUrl);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
unset($_SESSION['transactionId']);
|
||||
$request->getSession()->remove('transactionId');
|
||||
$errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage();
|
||||
$this->logger->critical($errorMessage);
|
||||
throw PaymentException::customerCanceled($transaction->getOrderTransaction()->getId(), $errorMessage);
|
||||
@@ -174,12 +177,12 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
||||
)->getEntities()->first();
|
||||
|
||||
if ($orderTransaction->getOrder()->getAmountTotal() > 0) {
|
||||
$transactionEntity = $this->transactionService->getByOrderId(
|
||||
$transactionEntity = $this->pluginTransactionService->getByOrderId(
|
||||
$orderTransaction->getOrder()->getId(),
|
||||
$context
|
||||
);
|
||||
|
||||
$vRPaymentTransaction = $this->transactionService->read(
|
||||
$vRPaymentTransaction = $this->pluginTransactionService->read(
|
||||
$transactionEntity->getTransactionId(),
|
||||
$transactionEntity->getSalesChannelId()
|
||||
);
|
||||
@@ -189,7 +192,7 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
||||
':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);
|
||||
}
|
||||
@@ -217,10 +220,240 @@ class VRPaymentPaymentHandler extends AbstractPaymentHandler
|
||||
string $paymentMethodId,
|
||||
Context $context
|
||||
): bool {
|
||||
if ($type === PaymentHandlerType::RECURRING) {
|
||||
return false;
|
||||
}
|
||||
// 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],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 => '7.0.1',
|
||||
self::PLUGIN_SYSTEM_VERSION => '7.1.0',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -56,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';
|
||||
@@ -457,7 +458,7 @@ class TransactionPayload extends AbstractPayload
|
||||
*/
|
||||
protected function addOptionalLineItems(array &$lineItems): void
|
||||
{
|
||||
if (count($this->order->getShippingCosts()->getCalculatedTaxes()) === 1) {
|
||||
if ($this->order->getShippingCosts() && $this->order->getShippingTotal() > 0) {
|
||||
if ($shippingLineItem = $this->getShippingLineItem()) {
|
||||
$lineItems[] = $shippingLineItem;
|
||||
}
|
||||
@@ -728,12 +729,21 @@ class TransactionPayload extends AbstractPayload
|
||||
{
|
||||
$lineItem = null;
|
||||
|
||||
$lineItemPriceTotal = array_sum(array_map(static function (LineItemCreate $lineItem) {
|
||||
return $lineItem->getAmountIncludingTax();
|
||||
}, $lineItems));
|
||||
// Calculate total of all current line items
|
||||
$lineItemPriceTotal = array_sum(array_map(static fn(LineItemCreate $li) => $li->getAmountIncludingTax(), $lineItems));
|
||||
|
||||
$adjustmentPrice = $this->order->getAmountTotal() - $lineItemPriceTotal;
|
||||
$adjustmentPrice = self::round($adjustmentPrice);
|
||||
$this->logger->debug("LineItem price total before adjustment: $lineItemPriceTotal");
|
||||
// Get shipping total including taxes from the order
|
||||
$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
|
||||
$hasShippingLineItem = array_filter($lineItems, static fn(LineItemCreate $li) => $li->getType() === LineItemType::SHIPPING);
|
||||
if (!$hasShippingLineItem && $shippingTotal > 0) {
|
||||
$lineItemPriceTotal += $shippingTotal;
|
||||
}
|
||||
|
||||
$adjustmentPrice = self::round($this->order->getAmountTotal() - $lineItemPriceTotal);
|
||||
|
||||
if (abs($adjustmentPrice) != 0) {
|
||||
if ($this->settings->isLineItemConsistencyEnabled()) {
|
||||
|
||||
@@ -5,12 +5,21 @@
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user