diff --git a/.gitignore b/.gitignore index 211663a..d942297 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ vendor/ -.vscode/ \ No newline at end of file +.vscode/ +localdev/ +*.code-workspace \ No newline at end of file diff --git a/Controller/Payment/InitiatePayment.php b/Controller/Payment/InitiatePayment.php new file mode 100644 index 0000000..487994e --- /dev/null +++ b/Controller/Payment/InitiatePayment.php @@ -0,0 +1,123 @@ +customerSession = $customerSession; + $this->logger = $logger; + $this->messageManager = $messageManager; + $this->nofrixionHelper = $helper; + $this->request = $request; + $this->resultPageFactory = $resultPageFactory; + $this->resultRedirectFactory = $resultRedirectFactory; + $this->checkoutSession = $checkoutSession; + $this->sessionManager = $sessionManager; + $this->url = $url; + } + + /** + * Summary of execute + * @return \Magento\Framework\Controller\Result\Redirect|\Magento\Framework\View\Result\Page + */ + public function execute() + { + //xdebug_break(); + + $resultRedirect = $this->resultRedirectFactory->create(); + $bankId = rawurldecode($this->request->getParam('bankId')); + + $order = $this->checkoutSession->getLastRealOrder(); + if ($order && $order->getId()) { + $paymentRequest = $this->nofrixionHelper->createPaymentRequest($order); + + $pendingPaymentStatus = OrderStatuses::STATUS_CODE_PENDING_PAYMENT; + $order->addCommentToStatusHistory('Forwarded customer to payment page', $pendingPaymentStatus); + $order->save(); + + // need to call: https://api-sandbox.nofrixion.com/api/v1/paymentrequests/{id}/pisp + // with body field 'ProviderID' = $bankId + // $amount = PreciseNumber::parseString((string) $paymentRequest['amount']); + $failureRedirectUrl = $this->url->getUrl('checkout/cart', ['_secure' => true]); + $paymentInitialization = $this->nofrixionHelper->initiatePayByBank($paymentRequest['id'], $bankId, $failureRedirectUrl, null); + + // Can't handle exception caused by null URL in controller so check with 'if' and 'filter_var' + if (filter_var($paymentInitialization->redirectUrl, FILTER_VALIDATE_URL)) { + $resultRedirect->setUrl($paymentInitialization->redirectUrl); + try { + // $resultRedirect used if forwarding to PIS provider + return $resultRedirect; + } catch (Exception $e) { + $this->logger->error('Error initializing payment.', ['exception' => $e]); + $this->initializationFailureAction($order, $paymentRequest, 'Order cancelled due to payment initialization error.'); + } + } else { + // or $paymentInitialization->redirectUrl is invalid. + $msg = 'A valid payment URL was not received for provider ' . $bankId; + $this->logger->error($msg); + $this->initializationFailureAction($order, $paymentRequest, $msg); + } + } else { + // If we get to here $order was not valid + $this->messageManager->addWarningMessage('An error occurred creating your order.'); + } + // if we haven't successfully directed to the payment URL return to cart. + $resultRedirect->setPath('checkout/cart'); + return $resultRedirect; + } + + public function initializationFailureAction($order, $paymentRequest, $message){ + // restore cart + $this->nofrixionHelper->restoreCart($order); + $this->messageManager->addWarningMessage($message); + + // Can't delete payment request as it has events (pisp_initiate). + // $this->nofrixionHelper->deletePaymentRequest($paymentRequest['id']); + } +} \ No newline at end of file diff --git a/Controller/Redirect/ForwardToPayment.php b/Controller/Redirect/ForwardToPayment.php deleted file mode 100644 index c742dcd..0000000 --- a/Controller/Redirect/ForwardToPayment.php +++ /dev/null @@ -1,78 +0,0 @@ -resultRedirectfactory = $resultRedirectFactory; - $this->checkoutSession = $checkoutSession; - $this->cookieMetadataFactory = $cookieMetadataFactory; - $this->cookieManager = $cookieManager; - $this->sessionManager = $sessionManager; - $this->nofrixionHelper = $helper; - $this->customerSession = $customerSession; - } - - private function setCookie($name, $value, $duration) - { - $path = $this->sessionManager->getCookiePath(); - $domain = $this->sessionManager->getCookieDomain(); - - $metadata = $this->cookieMetadataFactory->createPublicCookieMetadata()->setDuration($duration)->setPath($path)->setDomain($domain); - - $this->cookieManager->setPublicCookie($name, $value, $metadata); - } - - public function execute() - { - $order = $this->checkoutSession->getLastRealOrder(); - $resultRedirect = $this->resultRedirectfactory->create(); - - if ($order && $order->getId()) { - $paymentRequest = $this->nofrixionHelper->createPaymentRequest($order); - $url = $paymentRequest['hostedPayCheckoutUrl']; - - $pendingPaymentStatus = OrderStatuses::STATUS_CODE_PENDING_PAYMENT; - $order->addCommentToStatusHistory('Forwarding customer to the hosted payment page', $pendingPaymentStatus); - $order->save(); - - if (!$this->customerSession->isLoggedIn()) { - // Set cookies for the order/returns page - $duration = 30 * 24 * 60 * 60; - $this->setCookie('oar_order_id', $order->getIncrementId(), $duration); - $this->setCookie('oar_billing_lastname', $order->getBillingAddress()->getLastName(), $duration); - $this->setCookie('oar_email', $order->getCustomerEmail(), $duration); - } - - $resultRedirect->setUrl($url); - } else { - // Send back to the cart page - $resultRedirect->setUrl($order->getStore()->getUrl('checkout/cart')); - } - return $resultRedirect; - } - - -} diff --git a/Controller/Redirect/ReturnAfterPayment.php b/Controller/Redirect/ReturnAfterPayment.php index db47caa..91f2734 100644 --- a/Controller/Redirect/ReturnAfterPayment.php +++ b/Controller/Redirect/ReturnAfterPayment.php @@ -26,8 +26,16 @@ class ReturnAfterPayment implements \Magento\Framework\App\ActionInterface private LoggerInterface $logger; private RedirectFactory $resultRedirectFactory; - public function __construct(RequestInterface $request, LoggerInterface $logger, UrlInterface $url, RedirectFactory $resultRedirectFactory, PageFactory $resultPageFactory, Data $nofrixionHelper, OrderFactory $orderFactory, Session $checkoutSession) - { + public function __construct( + RequestInterface $request, + LoggerInterface $logger, + UrlInterface $url, + RedirectFactory $resultRedirectFactory, + PageFactory $resultPageFactory, + Data $nofrixionHelper, + OrderFactory $orderFactory, + Session $checkoutSession + ) { $this->request = $request; $this->resultRedirectFactory = $resultRedirectFactory; $this->url = $url; @@ -64,4 +72,4 @@ public function execute() return $resultRedirect; } -} +} \ No newline at end of file diff --git a/Controller/Webhook/In.php b/Controller/Webhook/In.php index 40ecb5a..97aa23c 100644 --- a/Controller/Webhook/In.php +++ b/Controller/Webhook/In.php @@ -9,19 +9,19 @@ use Magento\Framework\Controller\Result\JsonFactory; use Magento\Sales\Model\Order\Email\Sender\OrderSender; use Magento\Store\Model\StoreManagerInterface; -use Nofrixion\Payments\Helper\Data as NoFrixionHelper; +use Nofrixion\Payments\Helper\Data as NofrixionHelper; use Psr\Log\LoggerInterface; class In implements ActionInterface { private LoggerInterface $logger; - private NoFrixionHelper $nofrixionHelper; + private NofrixionHelper $nofrixionHelper; private OrderSender $orderSender; private RequestInterface $request; private StoreManagerInterface $storeManager; private JsonFactory $resultJsonFactory; - public function __construct(RequestInterface $request, NoFrixionHelper $helper, OrderSender $orderSender, StoreManagerInterface $storeManager, JsonFactory $resultJsonFactory, LoggerInterface $logger) + public function __construct(RequestInterface $request, NofrixionHelper $helper, OrderSender $orderSender, StoreManagerInterface $storeManager, JsonFactory $resultJsonFactory, LoggerInterface $logger) { $this->request = $request; $this->nofrixionHelper = $helper; diff --git a/Helper/Data.php b/Helper/Data.php index 58bbea0..128c5bd 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -4,11 +4,14 @@ namespace Nofrixion\Payments\Helper; +use Magento\Checkout\Model\Session; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\DB\TransactionFactory; use Magento\Framework\Exception\AlreadyExistsException; use Magento\Framework\UrlInterface; +use Magento\Quote\Model\QuoteFactory; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderManagementInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Invoice; use Magento\Sales\Model\Order\Payment; @@ -17,56 +20,122 @@ use Magento\Sales\Model\ResourceModel\Order\Status as StatusResource; use Magento\Sales\Model\ResourceModel\Order\StatusFactory as StatusResourceFactory; use Magento\Store\Model\ScopeInterface; -use NoFrixion\Client\PaymentRequest; +use Magento\Store\Model\StoreManagerInterface; +use Nofrixion\Client\PaymentRequestClient; +use Nofrixion\Client\MerchantClient; +use Nofrixion\Model\PaymentRequests\PaymentInitiationResponse; use Nofrixion\Payments\Model\OrderStatuses; -use NoFrixion\Util\PreciseNumber; +use Nofrixion\Util\PreciseNumber; use Psr\Log\LoggerInterface; +/** + * Data Helper for managing NoFrixion MoneyMoov API calls from magento via the moneymoov-php library + * + * @todo create Models for payment request in moneymoov-php and implement here. + */ class Data { + private Session $checkoutSession; + /** + * scopeConfig - allows access to plugin configuration settings. + * @var ScopeConfigInterface + */ private ScopeConfigInterface $scopeConfig; + /** + * Summary of url + * @var UrlInterface + */ private UrlInterface $url; + /** + * Set uup logging + * @var LoggerInterface + */ private LoggerInterface $logger; + private OrderManagementInterface $orderManager; + /** + * Summary of orderRepository + * @var OrderRepository + */ private OrderRepository $orderRepository; + private QuoteFactory $quoteFactory; + /** + * Summary of transactionFactory + * @var TransactionFactory + */ private TransactionFactory $transactionFactory; + /** + * Summary of statusFactory + * @var Order\StatusFactory + */ private StatusFactory $statusFactory; + /** + * Summary of statusResourceFactory + * @var StatusResourceFactory + */ private StatusResourceFactory $statusResourceFactory; - - public const ORDER_ID_SEPARATOR = '-'; private StatusResource\CollectionFactory $statusCollectionFactory; + /** + * Summary of storeManager + * @var StoreManagerInterface + */ + private StoreManagerInterface $storeManager; + public const ORDER_ID_SEPARATOR = '-'; + /** + * Summary of statusCollectionFactory + * @var StatusResource\CollectionFactory + */ + + protected ?int $storeId; + + + /** + * Set up dependency injection from core magento framework + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Framework\UrlInterface $url + * @param \Magento\Sales\Model\OrderRepository $orderRepository + * @param \Magento\Framework\DB\TransactionFactory $transactionFactory + * @param \Psr\Log\LoggerInterface $logger + * @param \Magento\Sales\Model\Order\StatusFactory $statusFactory + * @param \Magento\Sales\Model\ResourceModel\Order\StatusFactory $statusResourceFactory + * @param \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory $statusCollectionFactory + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + */ public function __construct( + Session $checkoutSession, ScopeConfigInterface $scopeConfig, UrlInterface $url, OrderRepository $orderRepository, TransactionFactory $transactionFactory, LoggerInterface $logger, + OrderManagementInterface $orderManager, + QuoteFactory $quoteFactory, StatusFactory $statusFactory, StatusResourceFactory $statusResourceFactory, - \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory $statusCollectionFactory + \Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory $statusCollectionFactory, + StoreManagerInterface $storeManager ) { + $this->checkoutSession = $checkoutSession; $this->scopeConfig = $scopeConfig; $this->url = $url; $this->orderRepository = $orderRepository; $this->transactionFactory = $transactionFactory; $this->logger = $logger; + $this->orderManager = $orderManager; + $this->quoteFactory = $quoteFactory; $this->statusFactory = $statusFactory; $this->statusResourceFactory = $statusResourceFactory; $this->statusCollectionFactory = $statusCollectionFactory; - } + $this->storeManager = $storeManager; - public function getPaymentRequestClient(?int $storeId = null): PaymentRequest - { - if ($this->isProductionMode($storeId)) { - $apiToken = $this->scopeConfig->getValue('payment/nofrixion/api_token_production', ScopeInterface::SCOPE_STORE, $storeId); - } else { - $apiToken = $this->scopeConfig->getValue('payment/nofrixion/api_token_sandbox', ScopeInterface::SCOPE_STORE, $storeId); - } - $baseUrl = $this->getApiBaseUrl(); - $client = new PaymentRequest($baseUrl, $apiToken); - return $client; + $this->storeId = (int) $this->storeManager->getStore()->getId(); } + /** + * determine if store is using sandbox or production merchant token. + * @param mixed $storeId + * @return bool + */ public function isProductionMode(?int $storeId = null): bool { $paymentMode = $this->scopeConfig->getValue('payment/nofrixion/mode', ScopeInterface::SCOPE_STORE, $storeId); @@ -77,6 +146,10 @@ public function isProductionMode(?int $storeId = null): bool } } + /** + * Returns sandbox or production API url as appropriate to plugin mode. + * @return string + */ public function getApiBaseUrl(): string { if ($this->isProductionMode()) { @@ -86,7 +159,86 @@ public function getApiBaseUrl(): string } } + /** + * Returns sandbox or production merchant token as appropriate to plugin mode. + * @param mixed $storeId + * @return string + */ + public function getApiToken(?int $storeId = null): string + { + if ($this->isProductionMode($storeId)) { + $apiToken = $this->scopeConfig->getValue('payment/nofrixion/api_token_production', ScopeInterface::SCOPE_STORE, $storeId); + } else { + $apiToken = $this->scopeConfig->getValue('payment/nofrixion/api_token_sandbox', ScopeInterface::SCOPE_STORE, $storeId); + } + return $apiToken; + } + + /** + * Summary of getMerchantClient + * @param mixed $storeId + * @return \Nofrixion\Client\MerchantClient + */ + public function getMerchantClient(?int $storeId = null): MerchantClient + { + $apiToken = $this->getApiToken($storeId); + $baseUrl = $this->getApiBaseUrl(); + $client = new MerchantClient($baseUrl, $apiToken); + return $client; + } + /** + * Summary of getPayByBankSettings + * @return array + */ + public function getPayByBankSettings(): array + { + $storeId = (int) $this->storeManager->getStore()->getId(); + $client = $this->getMerchantClient($storeId); + $merchantId = $client->whoAmIMerchant()->id; + + $settings = $client->getMerchantPayByBankSettings($merchantId); + // quick filter base on currency, may not be needed after API update + $currency = $this->scopeConfig->getValue('payment/nofrixion/pisp_currency', ScopeInterface::SCOPE_STORE, $storeId); + + $this->logger->info('Found ' . count($settings) . ' PIS providers for merchant ID: ' . $merchantId); + + $settings = array_values(array_filter($settings, function($bank) use ($currency) { + return $bank->currency === $currency; + })); + + return $settings; + } + + public function initiatePayByBank( + string $paymentRequestId, + string $bankId, + ?string $redirectToOriginUrl, + ?PreciseNumber $amount + ): PaymentInitiationResponse { + $storeId = (int) $this->storeManager->getStore()->getId(); + $client = $this->getPaymentRequestClient($storeId); + return $client->initiatePayByBank($paymentRequestId, $bankId, $redirectToOriginUrl, $amount); + } + /** + * Summary of getPaymentRequestClient + * @param mixed $storeId + * @return \Nofrixion\Client\PaymentRequestClient + */ + public function getPaymentRequestClient(?int $storeId = null): PaymentRequestClient + { + $apiToken = $this->getApiToken($storeId); + $baseUrl = $this->getApiBaseUrl(); + $client = new PaymentRequestClient($baseUrl, $apiToken); + return $client; + } + + /** + * Summary of createPaymentRequest + * @param \Magento\Sales\Model\Order $order + * @return array + * @todo paymentMethodTypes should reflect method used at checkout. + */ public function createPaymentRequest(Order $order): array { $storeId = (int) $order->getStoreId(); @@ -116,12 +268,33 @@ public function createPaymentRequest(Order $order): array return $paymentRequest; } + /** + * Gets PaymentRequest by Payment Request Id + * @param string $id + * @param int $storeId + * @return array + */ public function getPaymentRequest(string $id, int $storeId): array { $client = $this->getPaymentRequestClient($storeId); return $client->getPaymentRequest($id); } + /** + * deletePaymentRequest - deletes a payment request + * @param string $id The payment request Id + */ + public function deletePaymentRequest(string $id) + { + $client = $this->getPaymentRequestClient($this->storeId); + return $client->deletePaymentRequest($id); + } + + /** + * Summary of processPayment + * @param array $paymentRequest + * @return \Magento\Sales\Api\Data\OrderInterface + */ public function processPayment(array $paymentRequest): OrderInterface { $nofrixionOrderId = $paymentRequest['orderID']; @@ -191,28 +364,49 @@ public function processPayment(array $paymentRequest): OrderInterface $saveTransaction->addObject($order); $saveTransaction->save(); } - - return $order; } + /** + * Summary of getPaymentRequestByOrderId + * @param mixed $nofrixionOrderId + * @return array + */ public function getPaymentRequestByOrderId(mixed $nofrixionOrderId): array { return $this->getPaymentRequestClient()->getPaymentRequestByOrderId($nofrixionOrderId); } + /** + * Summary of encodeOrderId + * @param \Magento\Sales\Model\Order $order + * @return string + */ public function encodeOrderId(Order $order): string { $r = $order->getId() . self::ORDER_ID_SEPARATOR . time(); return $r; } + /** + * Summary of decodeOrderId + * @param string $nofrixionOrderId + * @return string + */ public function decodeOrderId(string $nofrixionOrderId): string { $r = explode(self::ORDER_ID_SEPARATOR, $nofrixionOrderId)[0]; return $r; } + /** + * Summary of refund + * @param \Magento\Payment\Model\InfoInterface $payment + * @param mixed $amount + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return void + */ public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount) { if ($payment instanceof Payment) { @@ -235,7 +429,32 @@ public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount) } } + /** + * restoreCart - loads an order back into the magento cart (quote) + * @param Magento\Sales\Model\Order $order + * @return void + */ + public function restoreCart($order) + { + $quote = $this->quoteFactory->create()->loadByIdWithoutStore($order->getQuoteId()); + if ($quote->getId()) { + $quote->setIsActive(1)->setReservedOrderId(null)->save(); + $this->checkoutSession->replaceQuote($quote); + + // if we restore order to cart, also cancel order + $this->orderManager->cancel($order->getId()); + $order->setStatus(Order::STATE_CANCELED); + $order->addStatusToHistory(Order::STATE_CANCELED, '', false); + $order->save(); + } + } + /** + * Summary of addNewStatusToState + * @param string $state + * @param array $statusData + * @return void + */ public function addNewStatusToState(string $state, array $statusData): void { /* @var StatusResource $statusResource */ @@ -250,6 +469,11 @@ public function addNewStatusToState(string $state, array $statusData): void $status->assignState($state, false, true); } + /** + * Summary of fixOrderState + * @param \Magento\Sales\Api\Data\OrderInterface $order + * @return void + */ private function fixOrderState(OrderInterface $order): void { $status = $order->getStatus(); diff --git a/Model/Config/Source/Mode.php b/Model/Config/Source/Mode.php index 7bb59d3..377fd9f 100644 --- a/Model/Config/Source/Mode.php +++ b/Model/Config/Source/Mode.php @@ -8,7 +8,10 @@ class Mode implements \Magento\Framework\Data\OptionSourceInterface public function toOptionArray() { - return [['value' => '1', 'label' => __('Production Mode')], ['value' => '0', 'label' => __('Sandbox Mode')]]; + return [ + ['value' => '1', 'label' => __('Production Mode')], + ['value' => '0', 'label' => __('Sandbox Mode')], + ]; } -} +} \ No newline at end of file diff --git a/Model/Config/Source/PaymentOptions.php b/Model/Config/Source/PaymentOptions.php index 7776261..ddb1c8f 100644 --- a/Model/Config/Source/PaymentOptions.php +++ b/Model/Config/Source/PaymentOptions.php @@ -10,9 +10,9 @@ class PaymentOptions implements \Magento\Framework\Data\OptionSourceInterface public function toOptionArray() { return [ - ['value' => 'pisp', 'label' => __('Banks')], - ['value' => 'card', 'label' => __('Cards')], - ['value' => 'card,pisp', 'label' => __('Cards & Banks')], + ['value' => 'pisp', 'label' => __('Bank')], + // ['value' => 'card', 'label' => __('Cards')], + // ['value' => 'card,pisp', 'label' => __('Cards & Bank')], ]; } diff --git a/Model/Config/Source/PispCurrency.php b/Model/Config/Source/PispCurrency.php new file mode 100644 index 0000000..366c861 --- /dev/null +++ b/Model/Config/Source/PispCurrency.php @@ -0,0 +1,17 @@ + 'EUR', 'label' => 'Euro'], + ['value' => 'GBP', 'label' => 'GBP'], + ]; + } + +} \ No newline at end of file diff --git a/Model/Payment/Nofrixion.php b/Model/Payment/Nofrixion.php index a810798..0fb2b5c 100644 --- a/Model/Payment/Nofrixion.php +++ b/Model/Payment/Nofrixion.php @@ -18,7 +18,18 @@ class Nofrixion extends \Magento\Payment\Model\Method\AbstractMethod private NofrixionHelper $nofrixionHelper; - public function __construct(NofrixionHelper $nofrixionHelper, \Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory, \Magento\Payment\Helper\Data $paymentData, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, Logger $logger, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], DirectoryHelper $directory = null) + public function __construct( + NofrixionHelper $nofrixionHelper, + \Magento\Framework\Model\Context $context, + \Magento\Framework\Registry $registry, + \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, + \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory, + \Magento\Payment\Helper\Data $paymentData, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + Logger $logger, + \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + array $data = [], DirectoryHelper $directory = null) { parent::__construct($context, $registry, $extensionFactory, $customAttributeFactory, $paymentData, $scopeConfig, $logger, $resource, $resourceCollection, $data, $directory); $this->nofrixionHelper = $nofrixionHelper; diff --git a/Model/Ui/ConfigProvider.php b/Model/Ui/ConfigProvider.php index 700f90e..eac3e4f 100644 --- a/Model/Ui/ConfigProvider.php +++ b/Model/Ui/ConfigProvider.php @@ -5,15 +5,20 @@ use Magento\Checkout\Model\ConfigProviderInterface; use Magento\Framework\UrlInterface; +use Nofrixion\Payments\Helper\Data as NofrixionHelper; use Nofrixion\Payments\Model\Payment\Nofrixion; class ConfigProvider implements ConfigProviderInterface { + private NofrixionHelper $helper; private UrlInterface $url; - public function __construct(UrlInterface $url) - { + public function __construct( + NofrixionHelper $helper, + UrlInterface $url + ) { + $this->helper = $helper; $this->url = $url; } @@ -22,13 +27,14 @@ public function getConfig() $data = [ 'payment' => [ Nofrixion::CODE => [ - 'paymentRedirectUrl' => $this->url->getUrl('nofrixion/redirect/forwardToPayment', [ + 'paymentRedirectUrl' => $this->url->getUrl('nofrixion/payment/initiatepayment', [ '_secure' => true, '_nosid' => true - ]) + ]), + 'payByBankProviders' => $this->helper->getPayByBankSettings() ] ] ]; return $data; } -} +} \ No newline at end of file diff --git a/Observer/ReloadCartObserver.php b/Observer/ReloadCartObserver.php new file mode 100644 index 0000000..34cad55 --- /dev/null +++ b/Observer/ReloadCartObserver.php @@ -0,0 +1,41 @@ +checkoutSession = $checkoutSession; + $this->messageManager = $messageManager; + $this->nofrixionHelper = $helper; + } + + public function execute(Observer $observer) + { + // xdebug_break(); + + $message = 'Restored cart from abandoned or failed payment attempt. Please check your order and retry checkout.'; + $order = $this->checkoutSession->getLastRealOrder(); + $status = $order->getStatus(); + + // Comparing against a specific status code + if ($status == OrderStatuses::STATUS_CODE_PENDING_PAYMENT) { + $this->nofrixionHelper->restoreCart($order); + $this->messageManager->addWarningMessage($message); + } + } +} diff --git a/Plugin/Sales/Order/Email/Sender/OrderSenderPlugin.php b/Plugin/Sales/Order/Email/Sender/OrderSenderPlugin.php index 524f4ac..835a88b 100644 --- a/Plugin/Sales/Order/Email/Sender/OrderSenderPlugin.php +++ b/Plugin/Sales/Order/Email/Sender/OrderSenderPlugin.php @@ -26,7 +26,7 @@ public function aroundSend( $orderStatus = $order->getStatus(); $paymentMethodCode = $order->getPayment()->getMethod(); - // Proceed as normal for non-NoFrixion payment, or if payment status matches completed NoFrixion status. + // Proceed as normal for non-Nofrixion payment, or if payment status matches completed Nofrixion status. if ($paymentMethodCode != 'nofrixion' || $orderStatus === 'complete' || $orderStatus === 'nofrixion_paid_correctly') { $returnValue = $proceed($order, $forceSyncMode); } diff --git a/composer.json b/composer.json index ed39ed7..132810b 100644 --- a/composer.json +++ b/composer.json @@ -8,13 +8,12 @@ "email": "wouter.samaey@storefront.be" }, { - "name": "James Bramich", - "email": "james@nofrixion.com" + "name": "NoFrixion Development Team" } ], "license": "MIT", "require": { - "nofrixion/moneymoov-php": "^1.1.4" + "nofrixion/moneymoov-php": "^2.0.0" }, "autoload": { "files": [ diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 73f400c..35131e7 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -1,43 +1,81 @@ - - + + -
- +
+ - + Magento\Config\Model\Config\Source\Yesno - - - Nofrixion\Payments\Model\Config\Source\Mode - - - - - - - - - - - - - Nofrixion\Payments\Model\Config\Source\PaymentOptions - - + + + Nofrixion\Payments\Model\Config\Source\Mode + + + + + 1 + + + + + + 0 + + + + + Nofrixion\Payments\Model\Config\Source\PaymentOptions + + + + The name the customer sees for this payment method at checkout. + + pisp|card,pisp + + + + + + pisp|card,pisp + + Nofrixion\Payments\Model\Config\Source\PispCurrency + + + + The name the customer sees for this payment method at checkout. + + card|card,pisp + + + Magento\Payment\Model\Config\Source\Allspecificcountries - + Magento\Directory\Model\Config\Source\Country 1 - +
- + \ No newline at end of file diff --git a/etc/config.xml b/etc/config.xml index 05ccecc..11312fa 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -6,8 +6,10 @@ 1 0 Nofrixion\Payments\Model\Payment\Nofrixion - Nofrixion + Pay by Bank pisp + EUR + Pay by Card 0 offline diff --git a/etc/di.xml b/etc/di.xml index b89f1e7..1a61bb8 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -1,7 +1,10 @@ - + - - + + \ No newline at end of file diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml new file mode 100644 index 0000000..c6f7d46 --- /dev/null +++ b/etc/frontend/events.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml index 16bcd93..071b2de 100644 --- a/view/frontend/layout/checkout_index_index.xml +++ b/view/frontend/layout/checkout_index_index.xml @@ -1,7 +1,8 @@ - - + + - + @@ -18,11 +19,19 @@ - - Nofrixion_Payments/js/view/payment/nofrixion - - - true + + + Nofrixion_Payments/js/view/payment/nofrixion + + + + true @@ -41,8 +50,8 @@ - - - + + + - + \ No newline at end of file diff --git a/view/frontend/layout/default_head_block.xml b/view/frontend/layout/default_head_block.xml new file mode 100644 index 0000000..29708e1 --- /dev/null +++ b/view/frontend/layout/default_head_block.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/view/frontend/web/css/nofrixion-styles.css b/view/frontend/web/css/nofrixion-styles.css index f7db037..143055b 100644 --- a/view/frontend/web/css/nofrixion-styles.css +++ b/view/frontend/web/css/nofrixion-styles.css @@ -1,3 +1,102 @@ #spacer { - padding-left: 10%; + padding-left: 10%; } + +.hidden { + display: none; +} + +.bankButton { + background: white; + border-radius: 0.5rem; + border-width: 1px !important; + border-color: #cccccc; + margin-bottom: 0.5rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + width: auto; +} + +.bankButton:focus { + background: #eeeeee; + border: 1px solid #cccccc; +} + +.bankButton:hover { + background: #eeeeee; + border: 1px solid #cccccc; +} + +.nofrixion-prompt { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.nofrixion-payment-section { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.nofrixion-email-warning { + background-color: #FEE7EB; + padding: 16px; +} + +.nofrixion-login-prompt { + border: 1px solid #ccc; + padding: 16px; +} + +@-webkit-keyframes animate-svg-stroke-1 { + 0% { + stroke-dashoffset: 261.02130126953125px; + stroke-dasharray: 261.02130126953125px; + } + + 50% { + stroke-dashoffset: 0px; + stroke-dasharray: 261px; + } + + 100% { + stroke-dashoffset: -261px; + stroke-dasharray: 261px; + } + } + + @keyframes animate-svg-stroke-1 { + + 0% { + stroke-dashoffset: 261px; + stroke-dasharray: 261px; + } + + 50% { + stroke-dashoffset: 0px; + stroke-dasharray: 261px; + } + + 100% { + stroke-dashoffset: -261px; + stroke-dasharray: 261px; + } + } + + .svg-elem-1 { + -webkit-animation: animate-svg-stroke-1 2s cubic-bezier(0.65, 0.05, 0.36, 1) 0s both infinite; + animation: animate-svg-stroke-1 2s cubic-bezier(0.65, 0.05, 0.36, 1) 0s both infinite; + } + +.payment-method-processing-text { + font-family: Inter, sans-serif; color: #042A31; font-size: 1.5rem; margin-top: 1rem; +} + +#processing-animation { + text-align: center; + margin: 4rem; + height: 40%; +} \ No newline at end of file diff --git a/view/frontend/web/js/view/payment/method-renderer/nofrixion-bank.js b/view/frontend/web/js/view/payment/method-renderer/nofrixion-bank.js new file mode 100644 index 0000000..786cb4f --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/nofrixion-bank.js @@ -0,0 +1,43 @@ +define( + [ + 'jquery', + 'ko', + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/view/payment/default' + ], + function ($, ko, customer, Component) { + 'use strict'; + var self; + var baseRedirectUrl = window.checkoutConfig.payment.nofrixion.paymentRedirectUrl; + var payByBankProviderId = ''; + return Component.extend({ + defaults: { + template: 'Nofrixion_Payments/payment/nofrixion-bank' + }, + initialize: function () { + //initialize parent Component + this._super(); + self = this; + self.payByBankProviders = window.checkoutConfig.payment.nofrixion.payByBankProviders; + }, + isCustomerLoggedIn: customer.isLoggedIn, + isProcessing: ko.observable(false), + redirectAfterPlaceOrder: false, + initiatePayment: function (data, event) { + payByBankProviderId = data.personalInstitutionID; + if (self.placeOrder(data, event)) { + // placeOrder returns true if form validates + self.isProcessing(true); + }; + }, + getPayByBankProviders: function () { + return self.payByBankProviders; + }, + afterPlaceOrder: function () { + var url = baseRedirectUrl + '?bankId=' + encodeURIComponent(payByBankProviderId); + console.log('Redirecting to : ' + url); + $.mage.redirect(url); + } + }); + } +); diff --git a/view/frontend/web/js/view/payment/method-renderer/nofrixion-method.js b/view/frontend/web/js/view/payment/method-renderer/nofrixion-method.js deleted file mode 100644 index e2a8346..0000000 --- a/view/frontend/web/js/view/payment/method-renderer/nofrixion-method.js +++ /dev/null @@ -1,18 +0,0 @@ -define( - [ - 'jquery', - 'Magento_Checkout/js/view/payment/default' - ], - function ($, Component) { - 'use strict'; - return Component.extend({ - defaults: { - template: 'Nofrixion_Payments/payment/nofrixion', - }, - redirectAfterPlaceOrder: false, - afterPlaceOrder: function () { - $.mage.redirect(window.checkoutConfig.payment.nofrixion.paymentRedirectUrl); - } - }); - } -); diff --git a/view/frontend/web/js/view/payment/nofrixion.js b/view/frontend/web/js/view/payment/nofrixion.js index 7fd9092..cd8730a 100644 --- a/view/frontend/web/js/view/payment/nofrixion.js +++ b/view/frontend/web/js/view/payment/nofrixion.js @@ -11,7 +11,7 @@ define( rendererList.push( { type: 'nofrixion', - component: 'Nofrixion_Payments/js/view/payment/method-renderer/nofrixion-method' + component: 'Nofrixion_Payments/js/view/payment/method-renderer/nofrixion-bank' } ); return Component.extend({}); diff --git a/view/frontend/web/template/payment/nofrixion-bank.html b/view/frontend/web/template/payment/nofrixion-bank.html new file mode 100644 index 0000000..e6686dd --- /dev/null +++ b/view/frontend/web/template/payment/nofrixion-bank.html @@ -0,0 +1,85 @@ +
+
+ + + + + + + + + + +

Pay by bank allows you to make a payment directly from your bank account without using a credit + card or other payment methods.

+
+ + Pay by bank animation. + + +
+
+ + + +
+ +
+ + + + +
+ +
+ Email and billing address are required before proceeding with payment. +
+ +
+
+ Select your bank: +
+
+ + + +
+ +
+ +
+ + + +
+ +
+ + + +
+
+
+ + + +

+
+
+
\ No newline at end of file diff --git a/view/frontend/web/template/payment/nofrixion.html b/view/frontend/web/template/payment/nofrixion.html deleted file mode 100644 index db09f37..0000000 --- a/view/frontend/web/template/payment/nofrixion.html +++ /dev/null @@ -1,56 +0,0 @@ -
-
- - - - - - - - - - -

Pay by bank allows you to make a payment directly from your bank account without using a credit - card or other payment methods.

-
- - Pay by bank animation. - - -
-
- - - -
-
- - - -
- - - -
-
- - - -
-
-
- -
-
- -
-
\ No newline at end of file