diff --git a/Config.php b/Config.php index 50afba31dd1..c85632740fe 100644 --- a/Config.php +++ b/Config.php @@ -7,6 +7,7 @@ namespace Mollie\Payment; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Module\Manager; use Magento\Store\Model\ScopeInterface; @@ -81,16 +82,23 @@ class Config */ private $encryptor; + /** + * @var ProductMetadataInterface + */ + private $productMetadata; + public function __construct( ScopeConfigInterface $config, MollieLogger $logger, Manager $moduleManager, - EncryptorInterface $encryptor + EncryptorInterface $encryptor, + ProductMetadataInterface $productMetadata ) { $this->config = $config; $this->logger = $logger; $this->moduleManager = $moduleManager; $this->encryptor = $encryptor; + $this->productMetadata = $productMetadata; } /** @@ -141,6 +149,14 @@ public function getVersion() return $this->getPath(static::GENERAL_VERSION, null); } + /** + * Returns current version of Magento + */ + public function getMagentoVersion(): string + { + return $this->productMetadata->getVersion(); + } + /** * Returns API key * diff --git a/GraphQL/Resolver/Cart/AvailableIssuersForCart.php b/GraphQL/Resolver/Cart/AvailableIssuersForCart.php index beb5db7c0bb..03dc96ce744 100644 --- a/GraphQL/Resolver/Cart/AvailableIssuersForCart.php +++ b/GraphQL/Resolver/Cart/AvailableIssuersForCart.php @@ -16,28 +16,14 @@ class AvailableIssuersForCart implements ResolverInterface { - /** - * @var Mollie - */ - private $mollieModel; - - /** - * @var General - */ - private $mollieHelper; - /** * @var GetIssuers */ private $getIssuers; public function __construct( - Mollie $mollieModel, - General $mollieHelper, GetIssuers $getIssuers ) { - $this->mollieModel = $mollieModel; - $this->mollieHelper = $mollieHelper; $this->getIssuers = $getIssuers; } diff --git a/Helper/General.php b/Helper/General.php index 0da76a0ee0c..6d0a20de632 100755 --- a/Helper/General.php +++ b/Helper/General.php @@ -555,11 +555,11 @@ public function getPaymentLinkMessage($checkoutUrl, $storeId = null) /** * Order Currency and Value array for payment request * - * @param \Magento\Sales\Model\Order $order + * @param OrderInterface $order * - * @return array + * @return array{currency: string, value: string} */ - public function getOrderAmountByOrder($order) + public function getOrderAmountByOrder(OrderInterface $order): array { if ($this->useBaseCurrency($order->getStoreId())) { return $this->getAmountArray($order->getBaseCurrencyCode(), $order->getBaseGrandTotal()); @@ -579,16 +579,16 @@ public function useBaseCurrency($storeId = 0) } /** - * @param $currency - * @param $value + * @param string|null $currency + * @param float|null $value * - * @return array + * @return array{currency: string, value: string} */ - public function getAmountArray($currency, $value) + public function getAmountArray(?string $currency, ?float $value): array { return [ - "currency" => $currency, - "value" => $this->formatCurrencyValue($value, $currency) + 'currency' => $currency, + 'value' => $this->formatCurrencyValue($value, $currency) ]; } diff --git a/Logger/MollieLogger.php b/Logger/MollieLogger.php index 515c6b79800..70538f711c1 100644 --- a/Logger/MollieLogger.php +++ b/Logger/MollieLogger.php @@ -25,11 +25,11 @@ class MollieLogger extends Logger public function addInfoLog($type, $data) { if (is_array($data)) { - $this->addInfo($type . ': ' . json_encode($data)); + $this->addRecord(static::INFO, $type . ': ' . json_encode($data)); } elseif (is_object($data)) { - $this->addInfo($type . ': ' . json_encode($data)); + $this->addRecord(static::INFO, $type . ': ' . json_encode($data)); } else { - $this->addInfo($type . ': ' . $data); + $this->addRecord(static::INFO, $type . ': ' . $data); } } @@ -42,11 +42,11 @@ public function addInfoLog($type, $data) public function addErrorLog($type, $data) { if (is_array($data)) { - $this->addError($type . ': ' . json_encode($data)); + $this->addRecord(static::ERROR, $type . ': ' . json_encode($data)); } elseif (is_object($data)) { - $this->addError($type . ': ' . json_encode($data)); + $this->addRecord(static::ERROR, $type . ': ' . json_encode($data)); } else { - $this->addError($type . ': ' . $data); + $this->addRecord(static::ERROR, $type . ': ' . $data); } } } diff --git a/Model/Client/OrderProcessorInterface.php b/Model/Client/OrderProcessorInterface.php new file mode 100644 index 00000000000..a76c73a22d4 --- /dev/null +++ b/Model/Client/OrderProcessorInterface.php @@ -0,0 +1,20 @@ +orderLines = $orderLines; - $this->orderSender = $orderSender; $this->invoiceSender = $invoiceSender; - $this->invoiceService = $invoiceService; $this->orderRepository = $orderRepository; - $this->invoiceRepository = $invoiceRepository; $this->checkoutSession = $checkoutSession; $this->messageManager = $messageManager; $this->registry = $registry; @@ -241,13 +189,9 @@ public function __construct( $this->orderState = $orderState; $this->transaction = $transaction; $this->buildTransaction = $buildTransaction; - $this->config = $config; - $this->dashboardUrl = $dashboardUrl; - $this->transactionProcessor = $transactionProcessor; $this->eventManager = $eventManager; - $this->cancelOrder = $cancelOrder; - $this->addCardToVault = $addCardToVault; $this->paymentTokenForOrder = $paymentTokenForOrder; + $this->processTransaction = $processTransaction; } /** @@ -384,204 +328,15 @@ public function processResponse(Order $order, $mollieOrder) * @return array * @throws ApiException * @throws LocalizedException + * @deprecated since 3.0.0 */ public function processTransaction(Order $order, $mollieApi, $type = 'webhook', $paymentToken = null) { - $orderId = $order->getId(); - $storeId = $order->getStoreId(); - $transactionId = $order->getMollieTransactionId(); - $mollieOrder = $mollieApi->orders->get($transactionId, ['embed' => 'payments']); - $this->mollieHelper->addTolog($type, $mollieOrder); - $status = $mollieOrder->status; - - // This order is refunded, do not process any further. - if ($mollieOrder->payments() && $mollieOrder->payments()->offsetGet(0) && isset($mollieOrder->payments()->offsetGet(0)->metadata->refunded)) { - return ['success' => true, 'status' => $status, 'order_id' => $orderId, 'type' => $type]; - } - - if ($mollieOrder->isCompleted()) { - return ['success' => true, 'status' => $status, 'order_id' => $orderId, 'type' => $type]; - } + $result = $this->processTransaction->execute($order, $type); - $dashboardUrl = $this->dashboardUrl->forOrdersApi($order->getStoreId(), $mollieOrder->id); - $order->getPayment()->setAdditionalInformation('mollie_id', $mollieOrder->id); - $order->getPayment()->setAdditionalInformation('dashboard_url', $dashboardUrl); - - $this->orderLines->updateOrderLinesByWebhook($mollieOrder->lines, $mollieOrder->isPaid()); - - /** - * Check if last payment was canceled, failed or expired and redirect customer to cart for retry. - */ - $lastPaymentStatus = $this->mollieHelper->getLastRelevantStatus($mollieOrder); - if ($lastPaymentStatus == 'canceled' || $lastPaymentStatus == 'failed' || $lastPaymentStatus == 'expired') { - $method = $order->getPayment()->getMethodInstance()->getTitle(); - $order->getPayment()->setAdditionalInformation('payment_status', $lastPaymentStatus); - $this->orderRepository->save($order); - $this->cancelOrder->execute($order, $lastPaymentStatus); - $this->transactionProcessor->process($order, $mollieOrder); - $msg = ['success' => false, 'status' => $lastPaymentStatus, 'order_id' => $orderId, 'type' => $type, 'method' => $method]; - $this->mollieHelper->addTolog('success', $msg); - return $msg; - } - - $refunded = $mollieOrder->amountRefunded !== null ? true : false; - $payment = $order->getPayment(); - $this->addCardToVault->forPayment($payment, $mollieOrder); - if ($type == 'webhook' && $payment->getAdditionalInformation('payment_status') != $status) { - $payment->setAdditionalInformation('payment_status', $status); - $this->orderRepository->save($order); - } - - if (($mollieOrder->isPaid() || $mollieOrder->isAuthorized()) && !$refunded) { - $amount = $mollieOrder->amount->value; - $currency = $mollieOrder->amount->currency; - $orderAmount = $this->mollieHelper->getOrderAmountByOrder($order); - - if ($currency != $orderAmount['currency']) { - $msg = ['success' => false, 'status' => 'paid', 'order_id' => $orderId, 'type' => $type]; - $this->mollieHelper->addTolog('error', __('Currency does not match.')); - return $msg; - } - - if (!$payment->getIsTransactionClosed() && $type == 'webhook') { - if ($order->isCanceled()) { - $order = $this->mollieHelper->uncancelOrder($order); - } - - if (abs($amount - $orderAmount['value']) < 0.01) { - $payments = $mollieOrder->_embedded->payments; - $paymentId = end($payments)->id; - $payment->setTransactionId($paymentId); - $payment->setCurrencyCode($order->getBaseCurrencyCode()); - - if ($mollieOrder->isPaid()) { - $payment->setIsTransactionClosed(true); - $payment->registerCaptureNotification($order->getBaseGrandTotal(), true); - } - - if ($mollieOrder->isAuthorized() && - $this->mollieHelper->getInvoiceMoment($storeId) == InvoiceMoment::ON_AUTHORIZE - ) { - $payment->setIsTransactionClosed(false); - $payment->registerAuthorizationNotification($order->getBaseGrandTotal(), true); - - /** - * Create pending invoice, as order has not been paid. - */ - $invoice = $this->invoiceService->prepareInvoice($order); - $invoice->setRequestedCaptureCase(Invoice::NOT_CAPTURE); - $invoice->setTransactionId($paymentId); - $invoice->register(); - - $this->invoiceRepository->save($invoice); - } - - $order->setState(Order::STATE_PROCESSING); - $this->transactionProcessor->process($order, $mollieOrder); - - if ($mollieOrder->amountCaptured !== null) { - if ($mollieOrder->amount->currency != $mollieOrder->amountCaptured->currency) { - $message = __( - 'Mollie: Order Amount %1, Captured Amount %2', - $mollieOrder->amount->currency . ' ' . $mollieOrder->amount->value, - $mollieOrder->amountCaptured->currency . ' ' . $mollieOrder->amountCaptured->value - ); - $this->orderCommentHistory->add($order, $message); - } - } - - if (!$order->getIsVirtual()) { - $defaultStatusProcessing = $this->mollieHelper->getStatusProcessing($storeId); - if ($defaultStatusProcessing && ($defaultStatusProcessing != $order->getStatus())) { - $order->setStatus($defaultStatusProcessing); - } - } - - $this->orderRepository->save($order); - } - - /** @var Order\Invoice $invoice */ - $invoice = $payment->getCreatedInvoice(); - $sendInvoice = $this->mollieHelper->sendInvoice($storeId); - - if (!$order->getEmailSent()) { - try { - $this->orderSender->send($order, true); - $message = __('New order email sent'); - $this->orderCommentHistory->add($order, $message, true); - } catch (\Throwable $exception) { - $message = __('Unable to send the new order email: %1', $exception->getMessage()); - $this->orderCommentHistory->add($order, $message, false); - } - } - - if ($invoice && !$invoice->getEmailSent() && $sendInvoice) { - try { - $this->invoiceSender->send($invoice); - $message = __('Notified customer about invoice #%1', $invoice->getIncrementId()); - $this->orderCommentHistory->add($order, $message, true); - } catch (\Throwable $exception) { - $message = __('Unable to send the invoice: %1', $exception->getMessage()); - $this->orderCommentHistory->add($order, $message, true); - } - } - - } - - $msg = ['success' => true, 'status' => $status, 'order_id' => $orderId, 'type' => $type]; - $this->mollieHelper->addTolog('success', $msg); - $this->checkCheckoutSession($order, $paymentToken, $mollieOrder, $type); - return $msg; - } - - if ($refunded) { - $msg = ['success' => true, 'status' => 'refunded', 'order_id' => $orderId, 'type' => $type]; - $this->mollieHelper->addTolog('success', $msg); - return $msg; - } - - if ($mollieOrder->isCreated()) { - if ($mollieOrder->method == 'banktransfer' && !$order->getEmailSent()) { - try { - $this->orderSender->send($order); - $message = __('New order email sent'); - } catch (\Throwable $exception) { - $message = __('Unable to send the new order email: %1', $exception->getMessage()); - } - - if (!$statusPending = $this->mollieHelper->getStatusPendingBanktransfer($storeId)) { - $statusPending = $order->getStatus(); - } - - $order->setState(Order::STATE_PENDING_PAYMENT); - $this->transactionProcessor->process($order, $mollieOrder); - $order->addStatusToHistory($statusPending, $message, true); - $this->orderRepository->save($order); - } - $msg = ['success' => true, 'status' => $status, 'order_id' => $orderId, 'type' => $type]; - $this->mollieHelper->addTolog('success', $msg); - $this->checkCheckoutSession($order, $paymentToken, $mollieOrder, $type); - return $msg; - } - - if ($mollieOrder->isCanceled() || $mollieOrder->isExpired()) { - if ($type == 'webhook') { - $this->cancelOrder->execute($order, $status); - } - $msg = ['success' => false, 'status' => $status, 'order_id' => $orderId, 'type' => $type]; - $this->mollieHelper->addTolog('success', $msg); - return $msg; - } - - if ($mollieOrder->isCompleted()) { - $msg = ['success' => true, 'status' => $status, 'order_id' => $orderId, 'type' => $type]; - $this->mollieHelper->addTolog('success', $msg); - return $msg; - } - - $msg = ['success' => false, 'status' => $status, 'order_id' => $orderId, 'type' => $type]; - $this->mollieHelper->addTolog('success', $msg); - return $msg; + return [ + 'success' => $result->isSuccess(), + ]; } public function orderHasUpdate(OrderInterface $order, MollieApiClient $mollieApi) @@ -905,6 +660,7 @@ public function createOrderRefund(Order\Creditmemo $creditmemo, Order $order) ); } + /** @var int|float $remainderAmount */ $remainderAmount = $order->getPayment()->getAdditionalInformation('remainder_amount'); $maximumAmountToRefund = $order->getBaseGrandTotal() - $remainderAmount; if ($remainderAmount) { diff --git a/Model/Client/Orders/OrderProcessors.php b/Model/Client/Orders/OrderProcessors.php new file mode 100644 index 00000000000..155fb834c81 --- /dev/null +++ b/Model/Client/Orders/OrderProcessors.php @@ -0,0 +1,88 @@ +config = $config; + $this->processTransactionResponseFactory = $processTransactionResponseFactory; + $this->processors = $processors; + } + + public function process( + string $event, + OrderInterface $magentoOrder, + Order $mollieOrder, + string $type, + ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + if (!isset($this->processors[$event])) { + $response = $response ?? $this->returnResponse($mollieOrder, $magentoOrder, $type); + $this->config->addToLog('success', $response->toArray()); + return $response; + } + + foreach ($this->processors[$event] as $name => $processor) { + if (!$processor instanceof OrderProcessorInterface) { + throw new LocalizedException(__('"%1" does not implement %1', $name, OrderProcessorInterface::class)); + } + + $response = $processor->process($magentoOrder, $mollieOrder, $type, $response); + } + + if (!$response) { + $response = $this->returnResponse($mollieOrder, $magentoOrder, $type); + } + + $this->config->addToLog('success', $response->toArray()); + return $response; + } + + /** + * @param Order $mollieOrder + * @param OrderInterface $magentoOrder + * @param string $type + * @return ProcessTransactionResponse + */ + protected function returnResponse(Order $mollieOrder, OrderInterface $magentoOrder, string $type): ProcessTransactionResponse + { + return $this->processTransactionResponseFactory->create([ + 'success' => false, + 'status' => $mollieOrder->status, + 'order_id' => $magentoOrder->getEntityId(), + 'type' => $type, + ]); + } +} diff --git a/Model/Client/Orders/ProcessTransaction.php b/Model/Client/Orders/ProcessTransaction.php new file mode 100644 index 00000000000..cb41d298226 --- /dev/null +++ b/Model/Client/Orders/ProcessTransaction.php @@ -0,0 +1,158 @@ +processTransactionResponseFactory = $processTransactionResponseFactory; + $this->mollieApiClient = $mollieApiClient; + $this->mollieHelper = $mollieHelper; + $this->orderLines = $orderLines; + $this->orderProcessors = $orderProcessors; + } + + public function execute( + OrderInterface $order, + string $type = 'webhook' + ): ProcessTransactionResponse { + $mollieApi = $this->mollieApiClient->loadByStore((int)$order->getStoreId()); + $mollieOrder = $mollieApi->orders->get($order->getMollieTransactionId(), ['embed' => 'payments']); + $this->mollieHelper->addTolog($type, $mollieOrder); + $status = $mollieOrder->status; + + $defaultResponse = $this->processTransactionResponseFactory->create([ + 'success' => true, + 'status' => $status, + 'order_id' => $order->getEntityId(), + 'type' => $type + ]); + + $this->orderProcessors->process('preprocess', $order, $mollieOrder, $type, $defaultResponse); + + // This order is refunded, do not process any further. + if ($mollieOrder->payments() && + $mollieOrder->payments()->offsetGet(0) && + isset($mollieOrder->payments()->offsetGet(0)->metadata->refunded) + ) { + return $this->orderProcessors->process( + 'previously_refunded', + $order, + $mollieOrder, + $type, + $defaultResponse + ); + } + + if ($mollieOrder->isCompleted()) { + return $this->orderProcessors->process( + 'is_completed', + $order, + $mollieOrder, + $type, + $defaultResponse + ); + } + + $this->orderLines->updateOrderLinesByWebhook($mollieOrder->lines, $mollieOrder->isPaid()); + + /** + * Check if last payment was canceled, failed or expired and redirect customer to cart for retry. + */ + $lastPaymentStatus = $this->mollieHelper->getLastRelevantStatus($mollieOrder); + if ($lastPaymentStatus == 'canceled' || $lastPaymentStatus == 'failed' || $lastPaymentStatus == 'expired') { + return $this->orderProcessors->process( + 'last_payment_status_is_failure', + $order, + $mollieOrder, + $type, + $defaultResponse + ); + } + + $refunded = $mollieOrder->amountRefunded !== null; + if (($mollieOrder->isPaid() || $mollieOrder->isAuthorized()) && !$refunded) { + return $this->orderProcessors->process( + 'is_successful', + $order, + $mollieOrder, + $type, + $defaultResponse + ); + } + + if ($refunded) { + return $this->orderProcessors->process( + 'is_refunded', + $order, + $mollieOrder, + $type, + $this->processTransactionResponseFactory->create([ + 'success' => true, + 'status' => 'refunded', + 'order_id' => $order->getEntityId(), + 'type' => $type + ]) + ); + } + + if ($mollieOrder->isCreated()) { + return $this->orderProcessors->process('created', $order, $mollieOrder, $type, $defaultResponse); + } + + if ($mollieOrder->isCanceled()) { + return $this->orderProcessors->process('cancelled', $order, $mollieOrder, $type, $defaultResponse); + } + + if ($mollieOrder->isExpired()) { + return $this->orderProcessors->process('expired', $order, $mollieOrder, $type, $defaultResponse); + } + + throw new LocalizedException(__('Unable to process order %s', $order->getIncrementId())); + } +} diff --git a/Model/Client/Orders/Processors/AddAdditionalInformation.php b/Model/Client/Orders/Processors/AddAdditionalInformation.php new file mode 100644 index 00000000000..25c54143864 --- /dev/null +++ b/Model/Client/Orders/Processors/AddAdditionalInformation.php @@ -0,0 +1,62 @@ +dashboardUrl = $dashboardUrl; + $this->addCardToVault = $addCardToVault; + } + + public function process(OrderInterface $magentoOrder, Order $mollieOrder, string $type, ProcessTransactionResponse $response): ?ProcessTransactionResponse + { + if ($mollieOrder->payments() && + $mollieOrder->payments()->offsetGet(0) && + isset($mollieOrder->payments()->offsetGet(0)->metadata->refunded) + ) { + return $response; + } + + if ($mollieOrder->isCompleted()) { + return $response; + } + + $dashboardUrl = $this->dashboardUrl->forOrdersApi($magentoOrder->getStoreId(), $mollieOrder->id); + $magentoOrder->getPayment()->setAdditionalInformation('mollie_id', $mollieOrder->id); + $magentoOrder->getPayment()->setAdditionalInformation('dashboard_url', $dashboardUrl); + + $status = $mollieOrder->status; + $payment = $magentoOrder->getPayment(); + $this->addCardToVault->forPayment($payment, $mollieOrder); + if ($type == 'webhook' && $payment->getAdditionalInformation('payment_status') != $status) { + $payment->setAdditionalInformation('payment_status', $status); + } + + return $response; + } +} diff --git a/Model/Client/Orders/Processors/CancelledProcessor.php b/Model/Client/Orders/Processors/CancelledProcessor.php new file mode 100644 index 00000000000..5bdb458bdff --- /dev/null +++ b/Model/Client/Orders/Processors/CancelledProcessor.php @@ -0,0 +1,55 @@ +processTransactionResponseFactory = $processTransactionResponseFactory; + $this->cancelOrder = $cancelOrder; + } + + public function process( + OrderInterface $order, + Order $mollieOrder, + string $type, + ?ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + if ($type == 'webhook') { + $this->cancelOrder->execute($order, $mollieOrder->status); + } + + $result = [ + 'success' => false, + 'status' => $mollieOrder->status, + 'order_id' => $order->getEntityId(), + 'type' => $type + ]; + + return $this->processTransactionResponseFactory->create($result); + } +} diff --git a/Model/Client/Orders/Processors/LastPaymentStatusIsFailure.php b/Model/Client/Orders/Processors/LastPaymentStatusIsFailure.php new file mode 100644 index 00000000000..7362b0f5ad6 --- /dev/null +++ b/Model/Client/Orders/Processors/LastPaymentStatusIsFailure.php @@ -0,0 +1,84 @@ +mollieHelper = $mollieHelper; + $this->orderRepository = $orderRepository; + $this->cancelOrder = $cancelOrder; + $this->transactionProcessor = $transactionProcessor; + $this->processTransactionResponseFactory = $processTransactionResponseFactory; + } + + public function process( + OrderInterface $magentoOrder, + Order $mollieOrder, + string $type, + ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + $lastPaymentStatus = $this->mollieHelper->getLastRelevantStatus($mollieOrder); + $method = $magentoOrder->getPayment()->getMethodInstance()->getTitle(); + $magentoOrder->getPayment()->setAdditionalInformation('payment_status', $lastPaymentStatus); + $this->orderRepository->save($magentoOrder); + $this->cancelOrder->execute($magentoOrder, $lastPaymentStatus); + $this->transactionProcessor->process($magentoOrder, $mollieOrder); + + $result = [ + 'success' => false, + 'status' => $lastPaymentStatus, + 'order_id' => $magentoOrder->getEntityId(), + 'type' => $type, + 'method' => $method + ]; + + $this->mollieHelper->addTolog('success', $result); + return $this->processTransactionResponseFactory->create($result); + } +} diff --git a/Model/Client/Orders/Processors/SendConfirmationEmailForBanktransfer.php b/Model/Client/Orders/Processors/SendConfirmationEmailForBanktransfer.php new file mode 100644 index 00000000000..ea7681efe5d --- /dev/null +++ b/Model/Client/Orders/Processors/SendConfirmationEmailForBanktransfer.php @@ -0,0 +1,77 @@ +config = $config; + $this->processTransactionResponseFactory = $processTransactionResponseFactory; + $this->orderSender = $orderSender; + } + + public function process( + OrderInterface $order, + Order $mollieOrder, + string $type, + ?ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + $response = $this->processTransactionResponseFactory->create([ + 'success' => true, + 'status' => $mollieOrder->status, + 'order_id' => $order->getEntityId(), + 'type' => $type + ]); + + if ($mollieOrder->method != 'banktransfer' || $order->getEmailSent()) { + return $response; + } + + try { + $this->orderSender->send($order); + $message = __('New order email sent'); + } catch (\Throwable $exception) { + $message = __('Unable to send the new order email: %1', $exception->getMessage()); + } + + if (!$statusPending = $this->config->statusPendingBanktransfer($order->getStoreId())) { + $statusPending = $order->getStatus(); + } + + $order->setState(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT); + $order->addStatusToHistory($statusPending, $message, true); + + return $response; + } +} diff --git a/Model/Client/Orders/Processors/SuccessfulPayment.php b/Model/Client/Orders/Processors/SuccessfulPayment.php new file mode 100644 index 00000000000..33af5efa23f --- /dev/null +++ b/Model/Client/Orders/Processors/SuccessfulPayment.php @@ -0,0 +1,261 @@ +processTransactionResponseFactory = $processTransactionResponseFactory; + $this->request = $request; + $this->checkoutSession = $checkoutSession; + $this->mollieHelper = $mollieHelper; + $this->invoiceService = $invoiceService; + $this->invoiceRepository = $invoiceRepository; + $this->transactionProcessor = $transactionProcessor; + $this->orderCommentHistory = $orderCommentHistory; + $this->orderRepository = $orderRepository; + $this->sendOrderEmails = $sendOrderEmails; + } + + /** + * @param OrderInterface|\Magento\Sales\Model\Order $order + * @param MollieOrder $mollieOrder + * @param string $type + * @param ProcessTransactionResponse $response + * @throws \Magento\Framework\Exception\LocalizedException + * @return ProcessTransactionResponse|null + */ + public function process(OrderInterface $order, Order $mollieOrder, string $type, ProcessTransactionResponse $response): ?ProcessTransactionResponse + { + $orderId = $order->getEntityId(); + $orderAmount = $this->mollieHelper->getOrderAmountByOrder($order); + + if ($mollieOrder->amount->currency != $orderAmount['currency']) { + $result = ['success' => false, 'status' => 'paid', 'order_id' => $orderId, 'type' => $type]; + $this->mollieHelper->addTolog('error', __('Currency does not match.')); + return $this->processTransactionResponseFactory->create($result); + } + + if (!$order->getPayment()->getIsTransactionClosed() && $type == 'webhook') { + $this->handleWebhookCall($order, $mollieOrder); + $this->sendOrderEmails($order); + } + + $result = ['success' => true, 'status' => $mollieOrder->status, 'order_id' => $orderId, 'type' => $type]; + $this->mollieHelper->addTolog('success', $result); + $this->checkCheckoutSession($order, $mollieOrder, $type); + return $this->processTransactionResponseFactory->create($result); + } + + /** + * @param OrderInterface|MagentoOrder $order + * @param MollieOrder $mollieOrder + * @throws \Magento\Framework\Exception\LocalizedException + * @return void + */ + private function handleWebhookCall(OrderInterface $order, MollieOrder $mollieOrder): void + { + if ($order->isCanceled()) { + $order = $this->mollieHelper->uncancelOrder($order); + } + + $orderAmount = $this->mollieHelper->getOrderAmountByOrder($order); + if (!(abs($mollieOrder->amount->value - $orderAmount['value']) < 0.01)) { + return; + } + + $payments = $mollieOrder->_embedded->payments; + $paymentId = end($payments)->id; + + /** @var OrderPaymentInterface|Payment $payment */ + $payment = $order->getPayment(); + $payment->setTransactionId($paymentId); + $payment->setCurrencyCode($order->getBaseCurrencyCode()); + + if ($mollieOrder->isPaid()) { + $payment->setIsTransactionClosed(true); + $payment->registerCaptureNotification($order->getBaseGrandTotal(), true); + } + + if ($mollieOrder->isAuthorized() && + $this->mollieHelper->getInvoiceMoment($order->getStoreId()) == InvoiceMoment::ON_AUTHORIZE + ) { + $payment->setIsTransactionClosed(false); + $payment->registerAuthorizationNotification($order->getBaseGrandTotal(), true); + $this->createPendingInvoice($order, $paymentId); + } + + $order->setState(MagentoOrder::STATE_PROCESSING); + $this->transactionProcessor->process($order, $mollieOrder); + + if ($mollieOrder->amountCaptured !== null && + $mollieOrder->amount->currency != $mollieOrder->amountCaptured->currency + ) { + $message = __( + 'Mollie: Order Amount %1, Captured Amount %2', + $mollieOrder->amount->currency . ' ' . $mollieOrder->amount->value, + $mollieOrder->amountCaptured->currency . ' ' . $mollieOrder->amountCaptured->value + ); + + $this->orderCommentHistory->add($order, $message); + } + + $this->setOrderStatus($order); + $this->orderRepository->save($order); + } + + public function checkCheckoutSession( + OrderInterface $order, + MollieOrder $paymentData, + string $type + ): void { + if ($type == 'webhook') { + return; + } + + $paymentToken = $this->request->getParam('payment_token'); + + if ($this->checkoutSession->getLastOrderId() != $order->getId()) { + if ($paymentToken && isset($paymentData->metadata->payment_token)) { + if ($paymentToken == $paymentData->metadata->payment_token) { + $this->checkoutSession->setLastQuoteId($order->getQuoteId()) + ->setLastSuccessQuoteId($order->getQuoteId()) + ->setLastOrderId($order->getId()) + ->setLastRealOrderId($order->getIncrementId()); + } + } + } + } + + /** + * @param OrderInterface|\Magento\Sales\Model\Order $order + */ + protected function sendOrderEmails(OrderInterface $order): void + { + $this->sendOrderEmails->sendOrderConfirmation($order); + + if ($invoice = $order->getPayment()->getCreatedInvoice()) { + $this->sendOrderEmails->sendInvoiceEmail($invoice); + } + } + + /** + * @param $order + * @param $paymentId + * @throws \Magento\Framework\Exception\LocalizedException + * @return void + */ + private function createPendingInvoice($order, $paymentId): void + { + /** + * Create pending invoice, as order has not been paid. + */ + $invoice = $this->invoiceService->prepareInvoice($order); + $invoice->setRequestedCaptureCase(Invoice::NOT_CAPTURE); + $invoice->setTransactionId($paymentId); + $invoice->register(); + + $this->invoiceRepository->save($invoice); + } + + /** + * @param $order + * @return void + */ + private function setOrderStatus($order): void + { + $defaultStatusProcessing = $this->mollieHelper->getStatusProcessing($order->getStoreId()); + if (!$order->getIsVirtual() && + $defaultStatusProcessing && + $defaultStatusProcessing != $order->getStatus() + ) { + $order->setStatus($defaultStatusProcessing); + } + } +} diff --git a/Model/Client/PaymentProcessorInterface.php b/Model/Client/PaymentProcessorInterface.php new file mode 100644 index 00000000000..8b3ae6a3863 --- /dev/null +++ b/Model/Client/PaymentProcessorInterface.php @@ -0,0 +1,20 @@ +config = $config; + $this->processTransactionResponseFactory = $processTransactionResponseFactory; + $this->processors = $processors; + } + + public function process( + string $event, + OrderInterface $magentoOrder, + Payment $molliePayment, + string $type, + ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + if (!isset($this->processors[$event])) { + $response = $response ?? $this->returnResponse($molliePayment, $magentoOrder, $type); + $this->config->addToLog('success', $response->toArray()); + return $response; + } + + foreach ($this->processors[$event] as $name => $processor) { + if (!$processor instanceof PaymentProcessorInterface) { + throw new LocalizedException(__('"%1" does not implement %1', $name, PaymentProcessorInterface::class)); + } + + $response = $processor->process($magentoOrder, $molliePayment, $type, $response); + } + + if (!$response) { + $response = $this->returnResponse($molliePayment, $magentoOrder, $type); + } + + $this->config->addToLog('success', $response->toArray()); + return $response; + } + + /** + * @param Payment $molliePayment + * @param OrderInterface $magentoOrder + * @param string $type + * @return ProcessTransactionResponse + */ + protected function returnResponse(Payment $molliePayment, OrderInterface $magentoOrder, string $type): ProcessTransactionResponse + { + return $this->processTransactionResponseFactory->create([ + 'success' => false, + 'status' => $molliePayment->status, + 'order_id' => $magentoOrder->getEntityId(), + 'type' => $type, + ]); + } +} diff --git a/Model/Client/Payments/ProcessTransaction.php b/Model/Client/Payments/ProcessTransaction.php new file mode 100644 index 00000000000..f432eaa2a6b --- /dev/null +++ b/Model/Client/Payments/ProcessTransaction.php @@ -0,0 +1,128 @@ +processTransactionResponseFactory = $processTransactionResponseFactory; + $this->paymentProcessors = $paymentProcessors; + $this->mollieApiClient = $mollieApiClient; + $this->mollieHelper = $mollieHelper; + } + + public function execute( + OrderInterface $magentoOrder, + string $type = 'webhook' + ): ProcessTransactionResponse { + $mollieApi = $this->mollieApiClient->loadByStore((int)$magentoOrder->getStoreId()); + $molliePayment = $mollieApi->payments->get($magentoOrder->getMollieTransactionId()); + $this->mollieHelper->addTolog($type, $molliePayment); + $status = $molliePayment->status; + + $defaultResponse = $this->processTransactionResponseFactory->create([ + 'success' => true, + 'status' => $status, + 'order_id' => $magentoOrder->getEntityId(), + 'type' => $type + ]); + + $this->paymentProcessors->process( + 'preprocess', + $magentoOrder, + $molliePayment, + $type, + $defaultResponse + ); + + $refunded = isset($molliePayment->_links->refunds) ? true : false; + if ($status == 'paid' && !$refunded) { + return $this->paymentProcessors->process( + 'paid', + $magentoOrder, + $molliePayment, + $type, + $defaultResponse + ); + } + + if ($refunded) { + return $this->paymentProcessors->process( + 'refunded', + $magentoOrder, + $molliePayment, + $type, + $defaultResponse + ); + } + + if ($status == 'open') { + return $this->paymentProcessors->process( + 'open', + $magentoOrder, + $molliePayment, + $type, + $defaultResponse + ); + } + + if ($status == 'pending') { + return $this->paymentProcessors->process( + 'pending', + $magentoOrder, + $molliePayment, + $type, + $defaultResponse + ); + } + + if ($status == 'canceled' || $status == 'failed' || $status == 'expired') { + return $this->paymentProcessors->process( + 'failed', + $magentoOrder, + $molliePayment, + $type, + $defaultResponse + ); + } + + throw new \Exception('Unknown status'); + } +} diff --git a/Model/Client/Payments/Processors/AddAdditionalInformation.php b/Model/Client/Payments/Processors/AddAdditionalInformation.php new file mode 100644 index 00000000000..6fe9c454fa9 --- /dev/null +++ b/Model/Client/Payments/Processors/AddAdditionalInformation.php @@ -0,0 +1,50 @@ +dashboardUrl = $dashboardUrl; + } + + public function process( + OrderInterface $order, + Payment $molliePayment, + string $type, + ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + $magentoPayment = $order->getPayment(); + $dashboardUrl = $this->dashboardUrl->forPaymentsApi($order->getStoreId(), $molliePayment->id); + $magentoPayment->setAdditionalInformation('dashboard_url', $dashboardUrl); + $magentoPayment->setAdditionalInformation('mollie_id', $molliePayment->id); + + $status = $molliePayment->status; + if ($type == 'webhook' && $magentoPayment->getAdditionalInformation('payment_status') != $status) { + $magentoPayment->setAdditionalInformation('payment_status', $status); + } + + if ($molliePayment->details !== null) { + $magentoPayment->setAdditionalInformation('details', json_encode($molliePayment->details)); + } + + return $response; + } +} diff --git a/Model/Client/Payments/Processors/FailedStatusProcessor.php b/Model/Client/Payments/Processors/FailedStatusProcessor.php new file mode 100644 index 00000000000..73955370a8d --- /dev/null +++ b/Model/Client/Payments/Processors/FailedStatusProcessor.php @@ -0,0 +1,75 @@ +cancelOrder = $cancelOrder; + $this->mollieHelper = $mollieHelper; + $this->transactionProcessor = $transactionProcessor; + $this->processTransactionResponseFactory = $processTransactionResponseFactory; + } + + public function process( + OrderInterface $magentoOrder, + Payment $molliePayment, + string $type, + ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + $status = $molliePayment->status; + if ($type == 'webhook') { + $this->cancelOrder->execute($magentoOrder, $status); + $this->transactionProcessor->process($magentoOrder, null, $molliePayment); + } + + $message = [ + 'success' => false, + 'status' => $status, + 'order_id' => $magentoOrder->getEntityId(), + 'type' => $type + ]; + + $this->mollieHelper->addTolog('success', $message); + + return $this->processTransactionResponseFactory->create($message); + } +} diff --git a/Model/Client/Payments/Processors/SendEmailForBanktransfer.php b/Model/Client/Payments/Processors/SendEmailForBanktransfer.php new file mode 100644 index 00000000000..4421f4ac607 --- /dev/null +++ b/Model/Client/Payments/Processors/SendEmailForBanktransfer.php @@ -0,0 +1,84 @@ +orderSender = $orderSender; + $this->transactionProcessor = $transactionProcessor; + $this->mollieHelper = $mollieHelper; + $this->processTransactionResponseFactory = $processTransactionResponseFactory; + } + + public function process( + OrderInterface $magentoOrder, + Payment $molliePayment, + string $type, + ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + if ($molliePayment->method != 'banktransfer' || $magentoOrder->getEmailSent()) { + return $response; + } + + try { + $this->orderSender->send($magentoOrder); + $message = __('New order email sent'); + } catch (\Throwable $exception) { + $message = __('Unable to send the new order email: %1', $exception->getMessage()); + } + + if (!$statusPending = $this->mollieHelper->getStatusPendingBanktransfer($magentoOrder->getStoreId())) { + $statusPending = $magentoOrder->getStatus(); + } + $magentoOrder->setState(Order::STATE_PENDING_PAYMENT); + $this->transactionProcessor->process($magentoOrder, null, $molliePayment); + $magentoOrder->addStatusToHistory($statusPending, $message, true); + + return $this->processTransactionResponseFactory->create([ + 'success' => true, + 'status' => 'open', + 'order_id' => $magentoOrder->getId(), + 'type' => $type, + ]); + } +} diff --git a/Model/Client/Payments/Processors/SuccessfulPayment.php b/Model/Client/Payments/Processors/SuccessfulPayment.php new file mode 100644 index 00000000000..5f5e7c06616 --- /dev/null +++ b/Model/Client/Payments/Processors/SuccessfulPayment.php @@ -0,0 +1,211 @@ +processTransactionResponseFactory = $processTransactionResponseFactory; + $this->orderAmount = $orderAmount; + $this->uncancel = $uncancel; + $this->transactionProcessor = $transactionProcessor; + $this->orderCommentHistory = $orderCommentHistory; + $this->mollieHelper = $mollieHelper; + $this->orderRepository = $orderRepository; + $this->orderSender = $orderSender; + $this->invoiceSender = $invoiceSender; + } + + public function process( + OrderInterface $magentoOrder, + Payment $molliePayment, + string $type, + ProcessTransactionResponse $response + ): ?ProcessTransactionResponse { + $amount = $molliePayment->amount->value; + $currency = $molliePayment->amount->currency; + + $orderAmount = $this->orderAmount->getByTransactionId($magentoOrder->getMollieTransactionId()); + if ($currency != $orderAmount['currency']) { + return $this->processTransactionResponseFactory->create([ + 'success' => false, + 'status' => 'paid', + 'order_id' => $magentoOrder->getId(), + 'type' => $type + ]); + } + + /** @var PaymentInterface|Order\Payment $payment */ + $payment = $magentoOrder->getPayment(); + if ($payment->getIsTransactionClosed() || $type != 'webhook') { + return $this->processTransactionResponseFactory->create([ + 'success' => true, + 'status' => 'paid', + 'order_id' => $magentoOrder->getId(), + 'type' => $type + ]); + } + + if ($magentoOrder->isCanceled()) { + $this->uncancel->execute($magentoOrder); + } + + if (abs($amount - $orderAmount['value']) < 0.01) { + $this->handlePayment($magentoOrder, $molliePayment); + } + + /** @var Order\Invoice $invoice */ + $invoice = $payment->getCreatedInvoice(); + $sendInvoice = $this->mollieHelper->sendInvoice($magentoOrder->getStoreId()); + + $this->sendOrderConfirmationEmail($magentoOrder); + $this->sendInvoiceEmail($invoice, $sendInvoice, $magentoOrder); + + return $this->processTransactionResponseFactory->create([ + 'success' => true, + 'status' => 'paid', + 'order_id' => $magentoOrder->getId(), + 'type' => $type + ]); + } + + private function handlePayment(OrderInterface $magentoOrder, Payment $molliePayment): void + { + /** @var PaymentInterface|Order\Payment $payment */ + $payment = $magentoOrder->getPayment(); + $payment->setCurrencyCode($magentoOrder->getBaseCurrencyCode()); + $payment->setTransactionId($magentoOrder->getMollieTransactionId()); + $payment->setIsTransactionClosed(true); + $payment->registerCaptureNotification($magentoOrder->getBaseGrandTotal(), true); + $magentoOrder->setState(Order::STATE_PROCESSING); + $this->transactionProcessor->process($magentoOrder, null, $molliePayment); + + if ($molliePayment->settlementAmount !== null) { + if ($molliePayment->amount->currency != $molliePayment->settlementAmount->currency) { + $message = __( + 'Mollie: Captured %1, Settlement Amount %2', + $molliePayment->amount->currency . ' ' . $molliePayment->amount->value, + $molliePayment->settlementAmount->currency . ' ' . $molliePayment->settlementAmount->value + ); + $this->orderCommentHistory->add($magentoOrder, $message); + } + } + + if (!$magentoOrder->getIsVirtual()) { + $defaultStatusProcessing = $this->mollieHelper->getStatusProcessing($magentoOrder->getStoreId()); + if ($defaultStatusProcessing && ($defaultStatusProcessing != $magentoOrder->getStatus())) { + $magentoOrder->setStatus($defaultStatusProcessing); + } + } + + $this->orderRepository->save($magentoOrder); + } + + /** + * @param OrderInterface|Order $order + */ + private function sendOrderConfirmationEmail(OrderInterface $order): void + { + if ($order->getEmailSent()) { + return; + } + + try { + $this->orderSender->send($order); + $message = __('New order email sent'); + $this->orderCommentHistory->add($order, $message, true); + } catch (\Throwable $exception) { + $message = __('Unable to send the new order email: %1', $exception->getMessage()); + $this->orderCommentHistory->add($order, $message, false); + } + } + + private function sendInvoiceEmail(Order\Invoice $invoice, bool $sendInvoice, OrderInterface $order): void + { + if ($invoice->getEmailSent() || !$sendInvoice) { + return; + } + + try { + $this->invoiceSender->send($invoice); + $message = __('Notified customer about invoice #%1', $invoice->getIncrementId()); + $this->orderCommentHistory->add($order, $message, true); + } catch (\Throwable $exception) { + $message = __('Unable to send the invoice: %1', $exception->getMessage()); + $this->orderCommentHistory->add($order, $message, true); + } + } +} diff --git a/Model/Client/ProcessTransactionResponse.php b/Model/Client/ProcessTransactionResponse.php new file mode 100644 index 00000000000..f613d1015bd --- /dev/null +++ b/Model/Client/ProcessTransactionResponse.php @@ -0,0 +1,84 @@ +success = $success; + $this->status = $status; + $this->order_id = $order_id; + $this->type = $type; + } + + /** + * @return bool + */ + public function isSuccess(): bool + { + return $this->success; + } + + /** + * @return string + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @return string + */ + public function getOrderId(): string + { + return $this->order_id; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + public function toArray(): array + { + return [ + 'success' => $this->success, + 'status' => $this->status, + 'order_id' => $this->order_id, + 'type' => $this->type, + ]; + } +} diff --git a/Model/Mollie.php b/Model/Mollie.php index 54e81fd05aa..23dd6edb682 100644 --- a/Model/Mollie.php +++ b/Model/Mollie.php @@ -30,6 +30,7 @@ use Mollie\Payment\Helper\General as MollieHelper; use Mollie\Payment\Model\Client\Orders as OrdersApi; use Mollie\Payment\Model\Client\Payments as PaymentsApi; +use Mollie\Payment\Model\Client\ProcessTransactionResponse; use Mollie\Payment\Service\Mollie\Timeout; use Psr\Log\LoggerInterface; @@ -298,7 +299,7 @@ public function getMollieApi($storeId = null) * @param string $type * @param null $paymentToken * - * @return array + * @return ProcessTransactionResponse * @throws LocalizedException * @throws \Mollie\Api\Exceptions\ApiException */ diff --git a/Model/OrderLines.php b/Model/OrderLines.php index ff43c11f200..d58142298e7 100644 --- a/Model/OrderLines.php +++ b/Model/OrderLines.php @@ -152,10 +152,10 @@ public function getOrderLinesByOrderId($orderId) } /** - * @param $orderLines + * @param array $orderLines * @param bool $paid */ - public function updateOrderLinesByWebhook($orderLines, $paid = false) + public function updateOrderLinesByWebhook(array $orderLines, bool $paid = false) { foreach ($orderLines as $line) { $orderLineRow = $this->getOrderLineByLineId($line->id); diff --git a/Service/Mollie/GetIssuers.php b/Service/Mollie/GetIssuers.php index e2ae2489ab7..fb3f86cdeb5 100644 --- a/Service/Mollie/GetIssuers.php +++ b/Service/Mollie/GetIssuers.php @@ -10,6 +10,7 @@ use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Locale\Resolver; use Mollie\Api\MollieApiClient; +use Mollie\Payment\Helper\General; use Mollie\Payment\Model\Mollie as MollieModel; class GetIssuers @@ -36,16 +37,23 @@ class GetIssuers */ private $resolver; + /** + * @var General + */ + private $general; + public function __construct( CacheInterface $cache, SerializerInterface $serializer, MollieModel $mollieModel, - Resolver $resolver + Resolver $resolver, + General $general ) { $this->cache = $cache; $this->serializer = $serializer; $this->mollieModel = $mollieModel; $this->resolver = $resolver; + $this->general = $general; } /** @@ -68,6 +76,9 @@ public function execute(MollieApiClient $mollieApi, $method, $type) $type ); + // $result will be a nested stdClass, this converts it on all levels to an array. + $result = json_decode(json_encode($result), true); + $this->cache->save( $this->serializer->serialize($result), $identifier, @@ -83,19 +94,29 @@ public function execute(MollieApiClient $mollieApi, $method, $type) * @param $method * @return array|null */ - public function getForGraphql($storeId, $method) + public function getForGraphql($storeId, $method): ?array { $mollieApi = $this->mollieModel->getMollieApi($storeId); - $issuers = $this->execute($mollieApi, $method, 'radio'); + $issuers = $this->execute( + $mollieApi, + $method, + $this->general->getIssuerListType($method) + ); + if (!$issuers) { return null; } $output = []; foreach ($issuers as $issuer) { - $issuer = (array)$issuer; - $issuer['image'] = (array)$issuer['image']; + if (!array_key_exists('image', $issuer)) { + $output[] = [ + 'name' => $issuer['name'], + 'code' => $issuer['id'] + ]; + continue; + } $output[] = [ 'name' => $issuer['name'], diff --git a/Service/Mollie/MollieApiClient.php b/Service/Mollie/MollieApiClient.php new file mode 100644 index 00000000000..f4e9e82c6ef --- /dev/null +++ b/Service/Mollie/MollieApiClient.php @@ -0,0 +1,61 @@ +config = $config; + $this->mollieApiClientFactory = $mollieApiClientFactory; + } + + public function loadByStore(int $storeId = null): \Mollie\Api\MollieApiClient + { + if (!class_exists('Mollie\Api\MollieApiClient')) { + throw new LocalizedException(__('Class Mollie\Api\MollieApiClient does not exist')); + } + + return $this->loadByApiKey($this->config->getApiKey($storeId)); + } + + public function loadByApiKey(string $apiKey): \Mollie\Api\MollieApiClient + { + if (isset($this->instances[$apiKey])) { + return $this->instances[$apiKey]; + } + + $mollieApiClient = $this->mollieApiClientFactory->create(); + $mollieApiClient->setApiKey($apiKey); + $mollieApiClient->addVersionString('Magento/' . $this->config->getMagentoVersion()); + $mollieApiClient->addVersionString('MollieMagento2/' . $this->config->getVersion()); + $this->instances[$apiKey] = $mollieApiClient; + + return $mollieApiClient; + } +} diff --git a/Service/Order/Lines/Generator/AmastyExtraFee.php b/Service/Order/Lines/Generator/AmastyExtraFee.php new file mode 100644 index 00000000000..54d6748736d --- /dev/null +++ b/Service/Order/Lines/Generator/AmastyExtraFee.php @@ -0,0 +1,53 @@ +mollieHelper = $mollieHelper; + } + + public function process(OrderInterface $order, array $orderLines): array + { + if (!method_exists($order->getExtensionAttributes(), 'getAmextrafeeBaseFeeAmount') || + $order->getExtensionAttributes()->getAmextrafeeBaseFeeAmount() === null + ) { + return $orderLines; + } + + $forceBaseCurrency = (bool)$this->mollieHelper->useBaseCurrency($order->getStoreId()); + $currency = $forceBaseCurrency ? $order->getBaseCurrencyCode() : $order->getOrderCurrencyCode(); + + $amount = $order->getExtensionAttributes()->getAmextrafeeFeeAmount(); + if ($forceBaseCurrency) { + $amount = $order->getExtensionAttributes()->getAmextrafeeBaseFeeAmount(); + } + + $orderLines[] = [ + 'type' => 'surcharge', + 'name' => 'Amasty Fee', + 'quantity' => 1, + 'unitPrice' => $this->mollieHelper->getAmountArray($currency, $amount), + 'totalAmount' => $this->mollieHelper->getAmountArray($currency, $amount), + 'vatRate' => 0, + 'vatAmount' => $this->mollieHelper->getAmountArray($currency, 0.0), + ]; + + return $orderLines; + } +} diff --git a/Service/Order/Lines/Order.php b/Service/Order/Lines/Order.php index c3830705213..523dff759da 100644 --- a/Service/Order/Lines/Order.php +++ b/Service/Order/Lines/Order.php @@ -143,7 +143,7 @@ private function getOrderLine(OrderItemInterface $item, $zeroPriceLine = false) * Should Match: (unitPrice × quantity) - discountAmount * NOTE: TotalAmount can differ from actutal Total Amount due to rouding in tax or exchange rate */ - $totalAmount = $this->getTotalAmountOrderItem($item); + $totalAmount = $this->getTotalAmountOrderItem($item) ?? 0.0; /** * The total discount amount of the line. @@ -237,7 +237,7 @@ protected function getShippingOrderLine(OrderInterface $order) * * @return float */ - private function getTotalAmountOrderItem(OrderItemInterface $item) + private function getTotalAmountOrderItem(OrderItemInterface $item): ?float { if ($item->getProductType() == ProductType::TYPE_BUNDLE) { return $this->forceBaseCurrency ? $item->getBaseRowTotalInclTax() : $item->getRowTotalInclTax(); diff --git a/Service/Order/Lines/PaymentFee.php b/Service/Order/Lines/PaymentFee.php index c576795ac28..2ee6c283212 100644 --- a/Service/Order/Lines/PaymentFee.php +++ b/Service/Order/Lines/PaymentFee.php @@ -48,7 +48,7 @@ public function orderHasPaymentFee(OrderInterface $order) * @param $forceBaseCurrency * @return array */ - public function getOrderLine(OrderInterface $order, $forceBaseCurrency) + public function getOrderLine(OrderInterface $order, $forceBaseCurrency): array { $currency = $forceBaseCurrency ? $order->getBaseCurrencyCode() : $order->getOrderCurrencyCode(); $amount = $order->getData('base_mollie_payment_fee'); diff --git a/Service/Order/SendOrderEmails.php b/Service/Order/SendOrderEmails.php new file mode 100644 index 00000000000..cce9592c2a1 --- /dev/null +++ b/Service/Order/SendOrderEmails.php @@ -0,0 +1,86 @@ +mollieHelper = $mollieHelper; + $this->orderSender = $orderSender; + $this->orderCommentHistory = $orderCommentHistory; + $this->invoiceSender = $invoiceSender; + } + + /** + * @param OrderInterface|Order $order + */ + public function sendOrderConfirmation(OrderInterface $order): void + { + if ($order->getEmailSent()) { + return; + } + + try { + $this->orderSender->send($order, true); + $message = __('New order email sent'); + $this->orderCommentHistory->add($order, $message, true); + } catch (\Throwable $exception) { + $message = __('Unable to send the new order email: %1', $exception->getMessage()); + $this->orderCommentHistory->add($order, $message, false); + } + } + + public function sendInvoiceEmail(InvoiceInterface $invoice): void + { + if ($invoice->getEmailSent() || + !$this->mollieHelper->sendInvoice($invoice->getStoreId()) + ) { + return; + } + + try { + $this->invoiceSender->send($invoice); + $message = __('Notified customer about invoice #%1', $invoice->getIncrementId()); + $this->orderCommentHistory->add($invoice->getOrder(), $message, true); + } catch (\Throwable $exception) { + $message = __('Unable to send the invoice: %1', $exception->getMessage()); + $this->orderCommentHistory->add($invoice->getOrder(), $message, true); + } + } +} diff --git a/Test/Fakes/FakeEncryptor.php b/Test/Fakes/FakeEncryptor.php new file mode 100644 index 00000000000..8fa8dff49ee --- /dev/null +++ b/Test/Fakes/FakeEncryptor.php @@ -0,0 +1,31 @@ +returnValues[$input] = $output; + } + + public function decrypt($data) + { + if (array_key_exists($data, $this->returnValues)) { + return $this->returnValues[$data]; + } + + return parent::decrypt($data); + } +} diff --git a/Test/Fakes/Model/Client/Orders/OrderProcessorsFake.php b/Test/Fakes/Model/Client/Orders/OrderProcessorsFake.php new file mode 100644 index 00000000000..e8e0f5627eb --- /dev/null +++ b/Test/Fakes/Model/Client/Orders/OrderProcessorsFake.php @@ -0,0 +1,78 @@ +processTransactionResponseFactory = $processTransactionResponseFactory; + + parent::__construct($config, $processTransactionResponseFactory, $processors); + } + + public function enableParentCall(): void + { + $this->shouldCallParent = true; + } + + public function disableParentCall(): void + { + $this->shouldCallParent = false; + } + + public function getCalledEvents(): array + { + return array_unique($this->calledEvents); + } + + public function process( + string $event, + OrderInterface $magentoOrder, + Order $mollieOrder, + string $type, + ProcessTransactionResponse $response = null + ): ?ProcessTransactionResponse { + $this->calledEvents[] = $event; + + if ($this->shouldCallParent) { + return parent::process($event, $magentoOrder, $mollieOrder, $type, $response); + } + + return $this->processTransactionResponseFactory->create([ + 'success' => false, + 'status' => 'fake', + 'order_id' => '-999', + 'type' => 'fake', + ]); + } +} diff --git a/Test/Fakes/Service/Mollie/FakeMollieApiClient.php b/Test/Fakes/Service/Mollie/FakeMollieApiClient.php new file mode 100644 index 00000000000..a79995a64e8 --- /dev/null +++ b/Test/Fakes/Service/Mollie/FakeMollieApiClient.php @@ -0,0 +1,31 @@ +instance = $instance; + } + + public function loadByStore(int $storeId = null): \Mollie\Api\MollieApiClient + { + if ($this->instance) { + return $this->instance; + } + + return parent::loadByStore($storeId); + } +} diff --git a/Test/Integration/Model/Client/Orders/Processors/SuccessfulPaymentTest.php b/Test/Integration/Model/Client/Orders/Processors/SuccessfulPaymentTest.php new file mode 100644 index 00000000000..8ce4507f5c9 --- /dev/null +++ b/Test/Integration/Model/Client/Orders/Processors/SuccessfulPaymentTest.php @@ -0,0 +1,237 @@ +loadOrder('100000001'); + + /** @var MollieOrderBuilder $orderBuilder */ + $orderBuilder = $this->objectManager->create(MollieOrderBuilder::class); + $orderBuilder->setAmount(100, 'USD'); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + + $result = $instance->process( + $order, + $orderBuilder->build(), + 'webhook', + $this->createResponse(false) + ); + + $this->assertFalse($result->isSuccess()); + $this->assertEquals('paid', $result->getStatus()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testTheTransactionIdIsSetOnThePayment() + { + $order = $this->loadOrder('100000001'); + + /** @var MollieOrderBuilder $orderBuilder */ + $orderBuilder = $this->objectManager->create(MollieOrderBuilder::class); + $orderBuilder->setAmount(100); + $orderBuilder->addPayment('payment_001'); + $orderBuilder->setStatus(OrderStatus::STATUS_CANCELED); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + + $instance->process( + $order, + $orderBuilder->build(), + 'webhook', + $this->createResponse(false) + ); + + $payment = $order->getPayment(); + + $this->assertEquals('payment_001', $payment->getTransactionId()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testACaptureNotificationIsRegistered() + { + $order = $this->loadOrder('100000001'); + + $this->assertNull($order->getTotalPaid()); + + /** @var MollieOrderBuilder $orderBuilder */ + $orderBuilder = $this->objectManager->create(MollieOrderBuilder::class); + $orderBuilder->setAmount(100); + $orderBuilder->addPayment('payment_001'); + $orderBuilder->setStatus(OrderStatus::STATUS_PAID); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + + $instance->process( + $order, + $orderBuilder->build(), + 'webhook', + $this->createResponse(false) + ); + + $this->assertEquals(100, $order->getTotalPaid()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testGeneratesInvoice() + { + $order = $this->loadOrder('100000001'); + + $this->assertNull($order->getPayment()->getCreatedInvoice()); + + /** @var MollieOrderBuilder $orderBuilder */ + $orderBuilder = $this->objectManager->create(MollieOrderBuilder::class); + $orderBuilder->setAmount(100); + $orderBuilder->addPayment('payment_001'); + $orderBuilder->setStatus(OrderStatus::STATUS_PAID); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + + $instance->process( + $order, + $orderBuilder->build(), + 'webhook', + $this->createResponse(false) + ); + + $this->assertInstanceOf(InvoiceInterface::class, $order->getPayment()->getCreatedInvoice()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testGeneratesInvoiceAndSendsEmail() + { + $order = $this->loadOrder('100000001'); + + $this->assertNull($order->getPayment()->getCreatedInvoice()); + + /** @var MollieOrderBuilder $orderBuilder */ + $orderBuilder = $this->objectManager->create(MollieOrderBuilder::class); + $orderBuilder->setAmount(100); + $orderBuilder->addPayment('payment_001'); + $orderBuilder->setStatus(OrderStatus::STATUS_PAID); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + + $instance->process( + $order, + $orderBuilder->build(), + 'webhook', + $this->createResponse(false) + ); + + $invoice = $order->getPayment()->getCreatedInvoice(); + $this->assertInstanceOf(InvoiceInterface::class, $invoice); + $this->assertTrue($invoice->getEmailSent(), 'The invoice email should be sent'); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testSendsConfirmationEmail() + { + $order = $this->loadOrder('100000001'); + + $this->assertNull($order->getEmailSent()); + + /** @var MollieOrderBuilder $orderBuilder */ + $orderBuilder = $this->objectManager->create(MollieOrderBuilder::class); + $orderBuilder->setAmount(100); + $orderBuilder->addPayment('payment_001'); + $orderBuilder->setStatus(OrderStatus::STATUS_PAID); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + + $instance->process( + $order, + $orderBuilder->build(), + 'webhook', + $this->createResponse(false) + ); + + $this->assertTrue($order->getEmailSent()); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testCanceledOrderGetsUncanceled(): void + { + $order = $this->loadOrder('100000001'); + $order->cancel(); + + $this->assertEquals(Order::STATE_CANCELED, $order->getState()); + + /** @var MollieOrderBuilder $orderBuilder */ + $orderBuilder = $this->objectManager->create(MollieOrderBuilder::class); + $orderBuilder->setAmount(100); + $orderBuilder->addPayment('payment_001'); + $orderBuilder->setStatus(OrderStatus::STATUS_PAID); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + + $instance->process( + $order, + $orderBuilder->build(), + 'webhook', + $this->createResponse(false) + ); + + $freshOrder = $this->objectManager->get(OrderInterface::class)->load($order->getId(), 'entity_id'); + + // There is a difference in ~2.3.4 and later, that's why we check both statuses as it is change somewhere in + // those versions. + $this->assertTrue(in_array( + $freshOrder->getState(), + [ + Order::STATE_PROCESSING, + Order::STATE_COMPLETE, + ] + ), 'We expect the order status to be "processing" or "complete".'); + } + + private function createResponse( + bool $succes, + string $status = 'paid', + string $type = 'webhook', + string $orderId = '100000001' + ) { + return $this->objectManager->create(\Mollie\Payment\Model\Client\ProcessTransactionResponse::class, [ + 'success' => $succes, + 'status' => $status, + 'order_id' => $orderId, + 'type' => $type + ]); + } +} diff --git a/Test/Integration/Model/Client/OrdersTest.php b/Test/Integration/Model/Client/OrdersTest.php index 8872b494111..84043e6564b 100644 --- a/Test/Integration/Model/Client/OrdersTest.php +++ b/Test/Integration/Model/Client/OrdersTest.php @@ -16,6 +16,7 @@ use Mollie\Payment\Model\Client\Orders; use Mollie\Payment\Model\OrderLines; use Mollie\Payment\Service\Order\OrderCommentHistory; +use Mollie\Payment\Test\Fakes\Service\Mollie\FakeMollieApiClient; use Mollie\Payment\Test\Integration\IntegrationTestCase; use stdClass; @@ -48,102 +49,6 @@ public function testCancelOrderThrowsAnExceptionWithTheOrderIdIncluded() $this->fail('We expected an exception but this was not thrown'); } - public function processTransactionProvider() - { - return [ - [ - 'USD', - OrderStatus::STATUS_PAID, - [ - 'Mollie: Order Amount %1, Captured Amount %2', - 'Notified customer about invoice #%1' - ] - ], - [ - 'EUR', - OrderStatus::STATUS_PAID, - [ - 'Notified customer about invoice #%1' - ] - ], - [ - 'EUR', - OrderStatus::STATUS_AUTHORIZED, - [ - 'New order email sent' - ] - ], - ]; - } - - /** - * @dataProvider processTransactionProvider - * @magentoDataFixture Magento/Sales/_files/order.php - * - * @param $currency - * @param $mollieOrderStatus - * @param $orderCommentHistoryMessages - * - * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Mollie\Api\Exceptions\ApiException - */ - public function testProcessTransaction($currency, $mollieOrderStatus, $orderCommentHistoryMessages) - { - $orderEndpointMock = $this->createMock(OrderEndpoint::class); - $orderEndpointMock->method('get')->willReturn($this->mollieOrderMock($mollieOrderStatus, $currency)); - - $mollieApiMock = $this->createMock(MollieApiClient::class); - $mollieApiMock->orders = $orderEndpointMock; - - $orderLinesMock = $this->createMock(OrderLines::class); - - $orderSenderMock = $this->createMock(OrderSender::class); - $orderSenderMock->method('send')->willReturn(true); - - $invoiceSenderMock = $this->createMock(InvoiceSender::class); - $invoiceSenderMock->method('send')->willReturn(true); - - $orderCommentHistoryMock = $this->createMock(OrderCommentHistory::class); - foreach ($orderCommentHistoryMessages as $index => $currentMessage) { - $orderCommentHistoryMock - ->expects($this->at($index)) - ->method('add') - ->with( - $this->isInstanceOf(OrderInterface::class), - $this->callback(function (Phrase $message) use ($currentMessage) { - $messageText = $message->getText(); - $expectedText = __($currentMessage)->getText(); - - if ($messageText != $expectedText) { - $this->fail('We expected "' . $messageText . '" but got "' . $expectedText . '"'); - } - - return $messageText == $expectedText; - }) - ); - } - - /** @var Orders $instance */ - $instance = $this->objectManager->create(Orders::class, [ - 'orderLines' => $orderLinesMock, - 'orderSender' => $orderSenderMock, - 'invoiceSender' => $invoiceSenderMock, - 'orderCommentHistory' => $orderCommentHistoryMock, - ]); - - $order = $this->loadOrder('100000001'); - $order->setBaseCurrencyCode($currency); - $order->setOrderCurrencyCode($currency); - - if ($mollieOrderStatus == OrderStatus::STATUS_PAID) { - $order->setEmailSent(1); - } - - $instance->processTransaction($order, $mollieApiMock); - - $this->assertEquals(Order::STATE_PROCESSING, $order->getState()); - } - public function testRemovesEmptySpaceFromThePrefix() { /** @var Orders $instance */ @@ -167,7 +72,10 @@ public function testRemovesEmptySpaceFromThePrefix() */ public function testProcessTransactionHandlesAStackOfPaymentsCorrectly() { + $this->loadFakeEncryptor()->addReturnValue('', 'test_dummyapikeythatisvalidandislongenough'); + $mollieOrderMock = $this->mollieOrderMock('N/A', 'EUR'); + $mollieOrderMock->lines = []; $mollieOrderMock->status = OrderStatus::STATUS_PAID; $mollieOrderMock->_embedded->payments = []; @@ -185,6 +93,12 @@ public function testProcessTransactionHandlesAStackOfPaymentsCorrectly() $mollieApiMock = $this->createMock(MollieApiClient::class); $mollieApiMock->orders = $orderEndpointMock; + /** @var FakeMollieApiClient $fakeMollieApiClient */ + $fakeMollieApiClient = $this->objectManager->get(FakeMollieApiClient::class); + $fakeMollieApiClient->setInstance($mollieApiMock); + + $this->objectManager->addSharedInstance($fakeMollieApiClient, \Mollie\Payment\Service\Mollie\MollieApiClient::class); + $invoiceSenderMock = $this->createMock(InvoiceSender::class); $invoiceSenderMock->method('send')->willReturn(true); @@ -243,6 +157,7 @@ public function testStartTransactionIncludesTheExpiresAtParameter() $cart->load('test01', 'reserved_order_id'); $order = $this->loadOrder('100000001'); + $order->setBaseCurrencyCode('EUR'); $order->setQuoteId($cart->getId()); $order->getPayment()->setMethod('mollie_methods_ideal'); diff --git a/Test/Integration/Model/Client/Payments/Processors/SuccessfulPaymentTest.php b/Test/Integration/Model/Client/Payments/Processors/SuccessfulPaymentTest.php new file mode 100644 index 00000000000..e61bd62a55d --- /dev/null +++ b/Test/Integration/Model/Client/Payments/Processors/SuccessfulPaymentTest.php @@ -0,0 +1,59 @@ +loadOrder('100000001'); + $order->setMollieTransactionId('abc123'); + $order->cancel(); + + $this->assertEquals(Order::STATE_CANCELED, $order->getState()); + + /** @var MolliePaymentBuilder $paymentBuilder */ + $paymentBuilder = $this->objectManager->create(MolliePaymentBuilder::class); + $paymentBuilder->setAmount($order->getBaseGrandTotal(), $order->getBaseCurrencyCode()); + + /** @var SuccessfulPayment $instance */ + $instance = $this->objectManager->create(SuccessfulPayment::class); + $instance->process( + $order, + $paymentBuilder->build(), + 'webhook', + $this->objectManager->create(ProcessTransactionResponse::class, [ + 'success' => true, + 'status' => 'paid', + 'order_id' => $order->getIncrementId(), + 'type' => 'webhook', + ]) + ); + + $freshOrder = $this->objectManager->get(OrderInterface::class)->load($order->getId(), 'entity_id'); + + // There is a difference in ~2.3.4 and later, that's why we check both statuses as it is change somewhere in + // those versions. + $this->assertTrue(in_array( + $freshOrder->getState(), + [ + Order::STATE_PROCESSING, + Order::STATE_COMPLETE, + ] + ), 'We expect the order status to be "processing" or "complete".'); + } +} diff --git a/Test/Integration/Model/Client/ProcessTransactionTest.php b/Test/Integration/Model/Client/ProcessTransactionTest.php new file mode 100644 index 00000000000..a588bb9c925 --- /dev/null +++ b/Test/Integration/Model/Client/ProcessTransactionTest.php @@ -0,0 +1,208 @@ +createMock(OrderLines::class); + + $orderSenderMock = $this->createMock(OrderSender::class); + $orderSenderMock->method('send')->willReturn(true); + + $invoiceSenderMock = $this->createMock(InvoiceSender::class); + $invoiceSenderMock->method('send')->willReturn(true); + + /** @var Orders\ProcessTransaction $instance */ + $instance = $this->objectManager->create(Orders\ProcessTransaction::class, [ + 'orderLines' => $orderLinesMock, + 'orderSender' => $orderSenderMock, + 'invoiceSender' => $invoiceSenderMock, + 'mollieApiClient' => $this->mollieClientMock($mollieOrderStatus, $currency), + ]); + + $order = $this->loadOrder('100000001'); + $order->setBaseCurrencyCode($currency); + $order->setOrderCurrencyCode($currency); + + if ($mollieOrderStatus == OrderStatus::STATUS_PAID) { + $order->setEmailSent(1); + } + + $instance->execute($order); + + $freshOrder = $this->objectManager->get(OrderInterface::class)->load($order->getId(), 'entity_id'); + + // Dump the comments in an array + $messages = array_map( function (OrderStatusHistoryInterface $history) { + return $history->getComment(); + }, $freshOrder->getStatusHistories()); + + foreach ($orderCommentHistoryMessages as $message) { + $parsedMessage = __($message, $freshOrder->getInvoiceCollection()->getLastItem()->getIncrementId()); + + $this->assertTrue( + in_array($parsedMessage, $messages), + sprintf('We expected the message "%s" to be in the history, but it is missing', $parsedMessage) + ); + } + + $this->assertEquals(Order::STATE_PROCESSING, $order->getState()); + } + + public function cancelsTheOrderProvider(): array + { + return [ + [OrderStatus::STATUS_CANCELED], + [OrderStatus::STATUS_EXPIRED], + ]; + } + + /** + * @dataProvider cancelsTheOrderProvider + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testCancelsTheOrder(string $state) + { + $apiClient = $this->mollieClientMock($state, 'EUR'); + + /** @var Orders\ProcessTransaction $instance */ + $instance = $this->objectManager->create(Orders\ProcessTransaction::class, [ + 'orderLines' => $this->createMock(OrderLines::class), + 'mollieApiClient' => $apiClient, + ]); + + $order = $this->loadOrder('100000001'); + $this->assertNotEquals(Order::STATE_CANCELED, $order->getState()); + + $instance->execute($order); + + $order = $this->loadOrder('100000001'); + $this->assertEquals(Order::STATE_CANCELED, $order->getState()); + } + + public function callsTheCorrectProcessorProvider(): array + { + return [ + [OrderStatus::STATUS_CREATED, 'created'], + ]; + } + + /** + * @dataProvider callsTheCorrectProcessorProvider + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testCallsTheCorrectProcessor(string $state, string $event) + { + $apiClient = $this->mollieClientMock($state, 'EUR'); + + /** @var OrderProcessorsFake $orderProcessorsFake */ + $orderProcessorsFake = $this->objectManager->create(OrderProcessorsFake::class); + $orderProcessorsFake->disableParentCall(); + + /** @var Orders\ProcessTransaction $instance */ + $instance = $this->objectManager->create(Orders\ProcessTransaction::class, [ + 'orderLines' => $this->createMock(OrderLines::class), + 'mollieApiClient' => $apiClient, + 'orderProcessors' => $orderProcessorsFake, + ]); + + $order = $this->loadOrder('100000001'); + + $instance->execute($order); + + $this->assertTrue( + in_array($event, $orderProcessorsFake->getCalledEvents()), + sprintf('The "%s" event is not called', $event) + ); + } + + protected function mollieClientMock(string $status, string $currency): MockObject + { + $mollieOrder = new \Mollie\Api\Resources\Order($this->createMock(MollieApiClient::class)); + $mollieOrder->lines = []; + $mollieOrder->status = $status; + $mollieOrder->amountCaptured = new \stdClass(); + $mollieOrder->amountCaptured->value = 50; + $mollieOrder->amountCaptured->currency = 'EUR'; + + $mollieOrder->amount = new \stdClass(); + $mollieOrder->amount->value = 100; + $mollieOrder->amount->currency = $currency; + + $mollieOrder->_embedded = new \stdClass; + $mollieOrder->_embedded->payments = [new \stdClass]; + $mollieOrder->_embedded->payments[0]->id = 'tr_abc1234'; + $mollieOrder->_embedded->payments[0]->status = 'success'; + + $orderEndpointMock = $this->createMock(OrderEndpoint::class); + $orderEndpointMock->method('get')->willReturn($mollieOrder); + + $mollieApiMock = $this->createMock(MollieApiClient::class); + $mollieApiMock->orders = $orderEndpointMock; + + $mollieClientMock = $this->createMock(\Mollie\Payment\Service\Mollie\MollieApiClient::class); + $mollieClientMock->method('loadByStore')->willReturn($mollieApiMock); + + return $mollieClientMock; + } +} diff --git a/Test/Integration/Model/Client/Processors/SendConfirmationEmailForBanktransferTest.php b/Test/Integration/Model/Client/Processors/SendConfirmationEmailForBanktransferTest.php new file mode 100644 index 00000000000..a5ec772cc04 --- /dev/null +++ b/Test/Integration/Model/Client/Processors/SendConfirmationEmailForBanktransferTest.php @@ -0,0 +1,61 @@ +createMock(OrderSender::class); + $orderSendMock->expects($this->once())->method('send'); + + /** @var SendConfirmationEmailForBanktransfer $instance */ + $instance = $this->objectManager->create(SendConfirmationEmailForBanktransfer::class, [ + 'orderSender' => $orderSendMock, + ]); + + $order = $this->loadOrder('100000001'); + + $mollieOrder = new Order(new MollieApiClient); + $mollieOrder->status = OrderStatus::STATUS_CREATED; + $mollieOrder->method = 'banktransfer'; + + $instance->process($order, $mollieOrder, 'webhook', null); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testDoesNothingWhenNotBanktransfer() + { + $orderSendMock = $this->createMock(OrderSender::class); + $orderSendMock->expects($this->never())->method('send'); + + /** @var SendConfirmationEmailForBanktransfer $instance */ + $instance = $this->objectManager->create(SendConfirmationEmailForBanktransfer::class, [ + 'orderSender' => $orderSendMock, + ]); + + $order = $this->loadOrder('100000001'); + + $mollieOrder = new Order(new MollieApiClient); + $mollieOrder->status = OrderStatus::STATUS_CREATED; + $mollieOrder->method = 'ideal'; + + $instance->process($order, $mollieOrder, 'webhook', null); + } +} diff --git a/Test/Integration/Model/Methods/VoucherTest.php b/Test/Integration/Model/Methods/VoucherTest.php index 53441494117..927ae12834b 100644 --- a/Test/Integration/Model/Methods/VoucherTest.php +++ b/Test/Integration/Model/Methods/VoucherTest.php @@ -7,6 +7,7 @@ namespace Mollie\Payment\Test\Integration\Model\Methods; use Magento\Quote\Api\Data\CartInterface; +use Mollie\Payment\Helper\General; use Mollie\Payment\Model\Methods\Voucher; use Mollie\Payment\Test\Integration\IntegrationTestCase; @@ -32,7 +33,6 @@ public function testIsNotAvailableWhenTheCategoryIsNotSet() } /** - * @magentoConfigFixture default_store payment/mollie_general/apikey_test test_dummyapikeywhichmustbe30characterslong * @magentoConfigFixture default_store payment/mollie_methods_voucher/category meal * @magentoConfigFixture default_store payment/mollie_methods_voucher/active 1 * @magentoConfigFixture default_store payment/mollie_general/enabled 1 @@ -41,9 +41,15 @@ public function testIsNotAvailableWhenTheCategoryIsNotSet() */ public function testIsAvailableWhenTheCategoryIsSet() { + $this->loadFakeEncryptor()->addReturnValue('', 'test_dummyapikeywhichmustbe30characterslong'); + /** @var CartInterface $cart */ $cart = $this->objectManager->create(CartInterface::class); + // When only running this test this isn't required, but when running this test in combination with other test + // this test will fail without this line. + $this->objectManager->addSharedInstance($this->objectManager->create(General::class), General::class); + /** @var Voucher $instance */ $instance = $this->objectManager->create(Voucher::class); $this->assertTrue($instance->isAvailable($cart)); diff --git a/Test/Integration/Model/OrderLinesTest.php b/Test/Integration/Model/OrderLinesTest.php index ef4ee850c07..1cf85952ebb 100644 --- a/Test/Integration/Model/OrderLinesTest.php +++ b/Test/Integration/Model/OrderLinesTest.php @@ -64,6 +64,7 @@ public function testCreditmemoUsesTheDiscount() /** @var CreditmemoInterface $creditmemo */ $creditmemo = $this->objectManager->get(CreditmemoInterface::class); + $creditmemo->setBaseCurrencyCode('EUR'); $creditmemo->setOrderId(999); $creditmemo->setItems([$creditmemoItem]); @@ -191,6 +192,7 @@ public function testHandlesNegativeDiscountAmounts() $orderItem->setBaseDiscountTaxCompensationAmount(-0.01); $order = $this->loadOrderById('100000001'); + $order->setBaseCurrencyCode('EUR'); $order->setItems([$orderItem]); /** @var OrderLines $instance */ diff --git a/Test/Integration/MollieOrderBuilder.php b/Test/Integration/MollieOrderBuilder.php new file mode 100644 index 00000000000..682baf72b8b --- /dev/null +++ b/Test/Integration/MollieOrderBuilder.php @@ -0,0 +1,70 @@ +client = new MollieApiClient(); + $this->order = new Order($this->client); + } + + public function addEmbedded(): void + { + if ($this->order->_embedded) { + return; + } + + $this->order->_embedded = new \StdClass; + } + + public function setAmount(float $value, $currency = 'EUR'): void + { + $this->order->amount = new \StdClass(); + $this->order->amount->value = $value; + $this->order->amount->currency = $currency; + } + + public function addPayment(string $id): void + { + $this->addEmbedded(); + + if (!isset($this->order->_embedded->payments)) { + $this->order->_embedded->payments = []; + } + + $payment = new Payment($this->client); + $payment->id = $id; + + $this->order->_embedded->payments[] = $payment; + } + + public function setStatus(string $status): void + { + $this->order->status = $status; + } + + public function build(): Order + { + return $this->order; + } +} diff --git a/Test/Integration/MolliePaymentBuilder.php b/Test/Integration/MolliePaymentBuilder.php new file mode 100644 index 00000000000..9f7ceca66f4 --- /dev/null +++ b/Test/Integration/MolliePaymentBuilder.php @@ -0,0 +1,41 @@ +client = new MollieApiClient(); + $this->payment = new Payment($this->client); + } + + public function setAmount(float $value, $currency = 'EUR'): void + { + $this->payment->amount = new \StdClass(); + $this->payment->amount->value = $value; + $this->payment->amount->currency = $currency; + } + + public function build(): Payment + { + return $this->payment; + } +} diff --git a/Test/Integration/PHPUnit/IntegrationTestCaseTrait.php b/Test/Integration/PHPUnit/IntegrationTestCaseTrait.php new file mode 100644 index 00000000000..9dad194da16 --- /dev/null +++ b/Test/Integration/PHPUnit/IntegrationTestCaseTrait.php @@ -0,0 +1,85 @@ +loadOrderById($incrementId); + } + + public function loadOrderById(string $orderId): OrderInterface + { + $repository = $this->objectManager->get(OrderRepositoryInterface::class); + $builder = $this->objectManager->create(SearchCriteriaBuilder::class); + $searchCriteria = $builder->addFilter('increment_id', $orderId, 'eq')->create(); + + $orderList = $repository->getList($searchCriteria)->getItems(); + + $order = array_shift($orderList); + $order->setBaseCurrencyCode('EUR'); + $order->setOrderCurrencyCode('EUR'); + + return $order; + } + + /** + * Load a custom fixture in the Test/Fixtures folder, and make it think it's inside the + * `dev/test/integration/testsuite` folder so it can rely on other fixtures. + * + * @param $path + * @throws \Exception + */ + public function loadFixture($path) + { + $cwd = getcwd(); + + $fullPath = __DIR__ . '/../../Fixtures/' . $path; + if (!file_exists($fullPath)) { + throw new \Exception('The path "' . $fullPath . '" does not exists'); + } + + chdir($this->getRootDirectory() . '/dev/tests/integration/testsuite/'); + require $fullPath; + chdir($cwd); + } + + public function getRootDirectory() + { + static $path; + + if (!$path) { + $directoryList = $this->objectManager->get(DirectoryList::class); + $path = $directoryList->getRoot(); + } + + return $path; + } + + public function loadFakeEncryptor(): FakeEncryptor + { + $instance = $this->objectManager->get(FakeEncryptor::class); + + $this->objectManager->addSharedInstance($instance, Encryptor::class); + + return $instance; + } +} diff --git a/Test/Integration/PHPUnit/IntegrationTestCaseVersion8AndLower.php b/Test/Integration/PHPUnit/IntegrationTestCaseVersion8AndLower.php index 4f3bd6fee30..170789e2f4d 100644 --- a/Test/Integration/PHPUnit/IntegrationTestCaseVersion8AndLower.php +++ b/Test/Integration/PHPUnit/IntegrationTestCaseVersion8AndLower.php @@ -11,14 +11,12 @@ use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\TestFramework\ObjectManager; +use Mollie\Payment\Test\Integration\PHPUnit\IntegrationTestCaseTrait; use PHPUnit\Framework\TestCase; class IntegrationTestCase extends TestCase { - /** - * @var ObjectManager - */ - protected $objectManager; + use IntegrationTestCaseTrait; protected function setUp() { @@ -43,71 +41,6 @@ protected function tearDownWithoutVoid() { } - /** - * @param $orderId - * @return \Magento\Sales\Model\Order - */ - public function loadOrderById($orderId) - { - $repository = $this->objectManager->get(OrderRepositoryInterface::class); - $builder = $this->objectManager->create(SearchCriteriaBuilder::class); - $searchCriteria = $builder->addFilter('increment_id', $orderId, 'eq')->create(); - - $orderList = $repository->getList($searchCriteria)->getItems(); - - return array_shift($orderList); - } - - /** - * Load a custom fixture in the Test/Fixtures folder, and make it think it's inside the - * `dev/test/integration/testsuite` folder so it can rely on other fixtures. - * - * @param $path - * @throws \Exception - */ - public function loadFixture($path) - { - $cwd = getcwd(); - - $fullPath = __DIR__ . '/../../Fixtures/' . $path; - if (!file_exists($fullPath)) { - throw new \Exception('The path "' . $fullPath . '" does not exists'); - } - - chdir($this->getRootDirectory() . '/dev/tests/integration/testsuite/'); - require $fullPath; - chdir($cwd); - } - - public function getRootDirectory() - { - static $path; - - if (!$path) { - $directoryList = $this->objectManager->get(DirectoryList::class); - $path = $directoryList->getRoot(); - } - - return $path; - } - - /** - * @return OrderInterface - */ - protected function loadOrder($incrementId) - { - /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ - $searchCriteriaBuilder = $this->objectManager->create(SearchCriteriaBuilder::class); - - /** @var OrderRepositoryInterface $order */ - $orderRepository = $this->objectManager->create(OrderRepositoryInterface::class); - - $searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', $incrementId, 'eq')->create(); - $orderList = $orderRepository->getList($searchCriteria)->getItems(); - - return array_shift($orderList); - } - public function assertStringContainsString($expected, $actual, $message = null) { $this->assertContains($expected, $actual, $message); diff --git a/Test/Integration/PHPUnit/IntegrationTestCaseVersion9AndHigher.php b/Test/Integration/PHPUnit/IntegrationTestCaseVersion9AndHigher.php index 80880cca296..bb09fb89269 100644 --- a/Test/Integration/PHPUnit/IntegrationTestCaseVersion9AndHigher.php +++ b/Test/Integration/PHPUnit/IntegrationTestCaseVersion9AndHigher.php @@ -11,14 +11,12 @@ use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\TestFramework\ObjectManager; +use Mollie\Payment\Test\Integration\PHPUnit\IntegrationTestCaseTrait; use PHPUnit\Framework\TestCase; class IntegrationTestCase extends TestCase { - /** - * @var ObjectManager - */ - protected $objectManager; + use IntegrationTestCaseTrait; protected function setUp(): void { @@ -42,69 +40,4 @@ protected function tearDown(): void protected function tearDownWithoutVoid() { } - - /** - * @param $orderId - * @return \Magento\Sales\Model\Order - */ - public function loadOrderById($orderId) - { - $repository = $this->objectManager->get(OrderRepositoryInterface::class); - $builder = $this->objectManager->create(SearchCriteriaBuilder::class); - $searchCriteria = $builder->addFilter('increment_id', $orderId, 'eq')->create(); - - $orderList = $repository->getList($searchCriteria)->getItems(); - - return array_shift($orderList); - } - - /** - * Load a custom fixture in the Test/Fixtures folder, and make it think it's inside the - * `dev/test/integration/testsuite` folder so it can rely on other fixtures. - * - * @param $path - * @throws \Exception - */ - public function loadFixture($path) - { - $cwd = getcwd(); - - $fullPath = __DIR__ . '/../../Fixtures/' . $path; - if (!file_exists($fullPath)) { - throw new \Exception('The path "' . $fullPath . '" does not exists'); - } - - chdir($this->getRootDirectory() . '/dev/tests/integration/testsuite/'); - require $fullPath; - chdir($cwd); - } - - public function getRootDirectory() - { - static $path; - - if (!$path) { - $directoryList = $this->objectManager->get(DirectoryList::class); - $path = $directoryList->getRoot(); - } - - return $path; - } - - /** - * @return OrderInterface - */ - protected function loadOrder($incrementId) - { - /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ - $searchCriteriaBuilder = $this->objectManager->create(SearchCriteriaBuilder::class); - - /** @var OrderRepositoryInterface $order */ - $orderRepository = $this->objectManager->create(OrderRepositoryInterface::class); - - $searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', $incrementId, 'eq')->create(); - $orderList = $orderRepository->getList($searchCriteria)->getItems(); - - return array_shift($orderList); - } } diff --git a/Test/Integration/Service/Mollie/GetIssuersTest.php b/Test/Integration/Service/Mollie/GetIssuersTest.php new file mode 100644 index 00000000000..d274e11c126 --- /dev/null +++ b/Test/Integration/Service/Mollie/GetIssuersTest.php @@ -0,0 +1,72 @@ +name = 'Issuer name'; + $method->id = 'IDID'; + + $mollieModelMock = $this->createMock(Mollie::class); + $mollieModelMock->method('getIssuers')->willReturn([$method]); + $mollieModelMock->method('getMollieApi')->willReturn(new MollieApiClient()); + + $cacheMock = $this->createMock(CacheInterface::class); + + /** @var GetIssuers $instance */ + $instance = $this->objectManager->create(GetIssuers::class, [ + 'mollieModel' => $mollieModelMock, + 'cache' => $cacheMock, + ]); + + $result = $instance->getForGraphql(1, 'mollie_methods_ideal'); + + $this->assertCount(1, $result); + $this->assertArrayNotHasKey('image', $result[0]); + $this->assertArrayNotHasKey('svg', $result[0]); + } + + public function testReturnsTheImageWhenAvailable(): void + { + $method = new \stdClass(); + $method->name = 'Issuer name'; + $method->id = 'IDID'; + $method->image = new \stdClass(); + $method->image->size2x = 'image.png'; + $method->image->svg = 'image.svg'; + + $mollieModelMock = $this->createMock(Mollie::class); + $mollieModelMock->method('getIssuers')->willReturn([$method]); + $mollieModelMock->method('getMollieApi')->willReturn(new MollieApiClient()); + + $cacheMock = $this->createMock(CacheInterface::class); + + /** @var GetIssuers $instance */ + $instance = $this->objectManager->create(GetIssuers::class, [ + 'mollieModel' => $mollieModelMock, + 'cache' => $cacheMock, + ]); + + $result = $instance->getForGraphql(1, 'mollie_methods_ideal'); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('image', $result[0]); + $this->assertEquals('image.png', $result[0]['image']); + + $this->assertArrayHasKey('svg', $result[0]); + $this->assertEquals('image.svg', $result[0]['svg']); + } +} diff --git a/Test/Integration/Service/Order/Lines/Generator/WeeFeeGeneratorTest.php b/Test/Integration/Service/Order/Lines/Generator/WeeFeeGeneratorTest.php index 3fb23281418..91a75b3de21 100644 --- a/Test/Integration/Service/Order/Lines/Generator/WeeFeeGeneratorTest.php +++ b/Test/Integration/Service/Order/Lines/Generator/WeeFeeGeneratorTest.php @@ -32,6 +32,7 @@ public function testDoesNothingWhenNotWeee() public function testReturnsWeeeItems() { $order = $this->loadOrder('100000001'); + $order->setBaseCurrencyCode('EUR'); $orderItems = $order->getItems(); $item = array_shift($orderItems); diff --git a/Test/Integration/Service/Order/Lines/OrderTest.php b/Test/Integration/Service/Order/Lines/OrderTest.php index 3e529f826f9..93182d91236 100644 --- a/Test/Integration/Service/Order/Lines/OrderTest.php +++ b/Test/Integration/Service/Order/Lines/OrderTest.php @@ -16,6 +16,7 @@ public function testGetOrderLinesProductCalculation() $this->loadFixture('Magento/Sales/order_item_list.php'); $order = $this->loadOrderById('100000001'); + $order->setBaseCurrencyCode('EUR'); /** @var Subject $instance */ $instance = $this->objectManager->get(Subject::class); @@ -43,6 +44,7 @@ public function testGetOrderLinesShippingFeeCalculation() $order->setBaseShippingAmount(10); $order->setShippingTaxAmount(2.1); $order->setBaseShippingTaxAmount(2.1); + $order->setBaseCurrencyCode('EUR'); /** @var Subject $instance */ $instance = $this->objectManager->get(Subject::class); @@ -66,6 +68,7 @@ public function testGetOrderLinesShippingFeeCalculation() public function testGeneratesAnEmptyLineForTheMainBundleProduct() { $order = $this->loadOrderById('100000001'); + $order->setBaseCurrencyCode('EUR'); /** @var Subject $instance */ $instance = $this->objectManager->get(Subject::class); @@ -92,6 +95,7 @@ public function testTheSimpleProductsHaveAPriceAvailable() } $order = $this->loadOrderById('100000001'); + $order->setOrderCurrencyCode('EUR'); /** @var Subject $instance */ $instance = $this->objectManager->get(Subject::class); @@ -114,6 +118,7 @@ public function testTheSkuGetsTruncated() { $sku = 'somereallyreallylongskunumberthatgetsautogeneratedandexceedthemaximumof64characters'; $order = $this->loadOrderById('100000001'); + $order->setBaseCurrencyCode('EUR'); $items = $order->getItems(); array_shift($items)->setSku($sku); @@ -133,6 +138,8 @@ public function testTheSkuGetsTruncated() public function testAddsAdjustmentsWhenTheTotalIsOff($adjustment) { $order = $this->loadOrderById('100000001'); + $order->setOrderCurrencyCode('EUR'); + $order->setBaseCurrencyCode('EUR'); $order->setBaseGrandTotal($order->getBaseGrandTotal() + $adjustment); foreach ($order->getItems() as $item) { @@ -162,6 +169,7 @@ public function testAddsAdjustmentsWhenTheTotalIsOff($adjustment) public function testDoesNotAddTheAdjustmentsWhenTheTotalIsMoreThanFiveCents() { $order = $this->loadOrderById('100000001'); + $order->setBaseCurrencyCode('EUR'); $order->setBaseGrandTotal($order->getBaseGrandTotal() + 0.06); foreach ($order->getItems() as $item) { diff --git a/Test/Integration/Service/Order/Lines/PaymentFeeTest.php b/Test/Integration/Service/Order/Lines/PaymentFeeTest.php index 76ee681325b..d7c6ebbc90f 100644 --- a/Test/Integration/Service/Order/Lines/PaymentFeeTest.php +++ b/Test/Integration/Service/Order/Lines/PaymentFeeTest.php @@ -30,6 +30,7 @@ public function testGetOrderLine() { /** @var OrderInterface $order */ $order = $this->objectManager->create(OrderInterface::class); + $order->setBaseCurrencyCode('EUR'); $order->setData('base_mollie_payment_fee', 1); $order->setData('base_mollie_payment_fee_tax', 0.21); diff --git a/composer.json b/composer.json index 9848ce95625..db6543f9cd9 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "mollie/magento2", "description": "Mollie Payment Module for Magento 2", - "version": "2.6.0", + "version": "2.7.0", "keywords": [ "mollie", "payment", diff --git a/etc/config.xml b/etc/config.xml index 7e730047d72..4bc7ddfde35 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -3,7 +3,7 @@ - v2.6.0 + v2.7.0 0 0 test diff --git a/etc/di.xml b/etc/di.xml index 83d20c03a2b..6d0a3acd764 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -7,11 +7,9 @@ - - @@ -21,9 +19,9 @@ - + - Magento\Framework\Filesystem\Driver\File + Magento\Framework\App\ProductMetadataInterface\Proxy @@ -129,6 +127,7 @@ Mollie\Payment\Service\Order\Lines\Generator\WeeeFeeGenerator Mollie\Payment\Service\Order\Lines\Generator\MageWorxRewardPoints + Mollie\Payment\Service\Order\Lines\Generator\AmastyExtraFee @@ -236,6 +235,62 @@ + + + Mollie\Payment\Model\Client\Orders\OrderProcessors + + + + + + Magento\Checkout\Model\Session\Proxy + + + + + + + + Mollie\Payment\Model\Client\Orders\Processors\AddAdditionalInformation + + + Mollie\Payment\Model\Client\Orders\Processors\LastPaymentStatusIsFailure + + + Mollie\Payment\Model\Client\Orders\Processors\SendConfirmationEmailForBanktransfer + + + Mollie\Payment\Model\Client\Orders\Processors\CancelledProcessor + + + Mollie\Payment\Model\Client\Orders\Processors\CancelledProcessor + + + Mollie\Payment\Model\Client\Orders\Processors\SuccessfulPayment + + + + + + + + + + Mollie\Payment\Model\Client\Orders\Processors\AddAdditionalInformation + + + Mollie\Payment\Model\Client\Payments\Processors\FailedStatusProcessor + + + Mollie\Payment\Model\Client\Payments\Processors\SendEmailForBanktransfer + + + Mollie\Payment\Model\Client\Payments\Processors\SuccessfulPayment + + + + + diff --git a/etc/schema.graphqls b/etc/schema.graphqls index 611b627a2ae..d5119ad9b7b 100644 --- a/etc/schema.graphqls +++ b/etc/schema.graphqls @@ -25,8 +25,8 @@ type SelectedPaymentMethod { type MollieIssuer { name: String code: String - image: String! - svg: String! + image: String + svg: String } type MolliePaymentMethodMeta { diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js index 869d2919022..fb17ce1c23d 100644 --- a/view/frontend/requirejs-config.js +++ b/view/frontend/requirejs-config.js @@ -3,6 +3,9 @@ var config = { mixins: { 'Magento_InstantPurchase/js/view/instant-purchase': { 'Mollie_Payment/js/view/instant-purchase/instant-purchase': true + }, + 'Onestepcheckout_Iosc/js/ajax': { + 'Mollie_Payment/js/mixin/onestepcheckout/ajax-mixin': true } } } diff --git a/view/frontend/web/js/mixin/onestepcheckout/ajax-mixin.js b/view/frontend/web/js/mixin/onestepcheckout/ajax-mixin.js new file mode 100644 index 00000000000..3a54d0095f1 --- /dev/null +++ b/view/frontend/web/js/mixin/onestepcheckout/ajax-mixin.js @@ -0,0 +1,38 @@ +define([ + 'mage/utils/wrapper', + 'uiRegistry', + 'Magento_Checkout/js/model/quote' +], function ( + wrapper, + Registry, + quote +) { + 'use strict'; + + /** + * When switching payment methods in the checkout we trigger a "save payment method" to calculate any + * applicable payment fees. When the One Step Checkout module is active this replaces the selected payment method + * with the previous version. So when you have iDeal selected, but select Credit Card, the module will still send + * iDeal which causes the wrong payment fee to be displayed. This code changes the payment method to the correct + * one. + */ + return function (Component) { + return Component.extend({ + collectParams: function () { + var result = this._super(); + + if (!result.paymentMethod || + !result.paymentMethod.method || + !quote.paymentMethod() || + !quote.paymentMethod().method + ) { + return result; + } + + result.paymentMethod.method = quote.paymentMethod().method; + + return result; + } + }) + } +});