diff --git a/.dev-tools/stubs/BackendAction.stub b/.dev-tools/stubs/BackendAction.stub new file mode 100644 index 0000000..1e23d81 --- /dev/null +++ b/.dev-tools/stubs/BackendAction.stub @@ -0,0 +1,266 @@ +_authorization = $context->getAuthorization(); + $this->_auth = $context->getAuth(); + $this->_helper = $context->getHelper(); + $this->_backendUrl = $context->getBackendUrl(); + $this->_formKeyValidator = $context->getFormKeyValidator(); + $this->_localeResolver = $context->getLocaleResolver(); + $this->_canUseBaseUrl = $context->getCanUseBaseUrl(); + $this->_session = $context->getSession(); + } + + /** + * Determine if current user is allowed to access this controller action. + * + * @return bool + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed(static::ADMIN_RESOURCE); + } + + /** + * Populate the layout menu item as active + * + * @param string $menuId + * @return $this + */ + protected function _setActiveMenu($menuId) + { + return $this; + } + + /** + * Generate crumbs for layout block + * + * @param string $label + * @param string $title + * @param string|null $link + * @return $this + */ + protected function _addBreadcrumb($label, $title, $link = null) + { + return $this; + } +} + +namespace Magento\Backend\App\Action; + +/** + * Shared context container passed into Backend Actions via Dependency Injection. + */ +class Context extends \Magento\Framework\App\Action\Context +{ + /** + * @return \Magento\Framework\AuthorizationInterface + */ + public function getAuthorization() {} + + /** + * @return \Magento\Backend\Model\Auth + */ + public function getAuth() {} + + /** + * @return \Magento\Backend\Helper\Data + */ + public function getHelper() {} + + /** + * @return \Magento\Backend\Model\UrlInterface + */ + public function getBackendUrl() {} + + /** + * @return \Magento\Framework\Data\Form\FormKey\Validator + */ + public function getFormKeyValidator() {} + + /** + * @return \Magento\Framework\Locale\ResolverInterface + */ + public function getLocaleResolver() {} + + /** + * @return bool + */ + public function getCanUseBaseUrl() {} + + /** + * @return \Magento\Backend\Model\Session + */ + public function getSession() {} +} + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Underlying Framework action hierarchy + */ +abstract class AbstractAction implements ActionInterface +{ + /** + * @var \Magento\Framework\App\RequestInterface + */ + protected $_request; + + /** + * @var \Magento\Framework\App\ResponseInterface + */ + protected $_response; + + /** + * @var \Magento\Framework\Controller\ResultFactory + */ + protected $resultFactory; + + /** + * @var \Magento\Framework\Controller\Result\RedirectFactory + */ + protected $resultRedirectFactory; + + /** + * @var \Magento\Framework\Message\ManagerInterface + */ + protected $messageManager; + + /** + * @param \Magento\Framework\App\Action\Context $context + */ + public function __construct(\Magento\Framework\App\Action\Context $context) + { + $this->_request = $context->getRequest(); + $this->_response = $context->getResponse(); + $this->resultFactory = $context->getResultFactory(); + $this->resultRedirectFactory = $context->getResultRedirectFactory(); + $this->messageManager = $context->getMessageManager(); + } + + /** + * @return \Magento\Framework\App\RequestInterface + */ + public function getRequest() + { + return $this->_request; + } + + /** + * @return \Magento\Framework\App\ResponseInterface + */ + public function getResponse() + { + return $this->_response; + } +} + +namespace Magento\Framework\App; + +/** + * Core execution interface for Magento controllers. + */ +interface ActionInterface +{ + /** + * Execute action based on request and return result + * + * @return \Magento\Framework\Controller\ResultInterface|\Magento\Framework\App\ResponseInterface + */ + public function execute(); +} + +namespace Magento\Framework\App\Action; + +/** + * Core Action Context definition + */ +class Context +{ + /** + * @return \Magento\Framework\App\RequestInterface + */ + public function getRequest() {} + + /** + * @return \Magento\Framework\App\ResponseInterface + */ + public function getResponse() {} + + /** + * @return \Magento\Framework\Controller\ResultFactory + */ + public function getResultFactory() {} + + /** + * @return \Magento\Framework\Controller\Result\RedirectFactory + */ + public function getResultRedirectFactory() {} + + /** + * @return \Magento\Framework\Message\ManagerInterface + */ + public function getMessageManager() {} +} diff --git a/.dev-tools/stubs/EmailTemplate.stub b/.dev-tools/stubs/EmailTemplate.stub new file mode 100644 index 0000000..726878c --- /dev/null +++ b/.dev-tools/stubs/EmailTemplate.stub @@ -0,0 +1,65 @@ +_templatesFactory = $templatesFactory; + $this->_emailConfig = $emailConfig; + } + +/** @return string */ +public function getPath() +{} + + /** + * Return array of options as key-value pairs for dropdown fields. + * + * @return array + */ + public function toOptionArray() + { + return [ + ['value' => 'string_template_id', 'label' => 'Template Label'] + ]; + } +} + +namespace Magento\Framework\Data; + +/** + * Core interface required for any UI component or adminhtml system config source model. + */ +interface OptionSourceInterface +{ + /** + * Return array of options as a multi-dimensional array containing value and label keys. + * + * @return array + */ + public function toOptionArray(); +} diff --git a/.dev-tools/stubs/OrderView.stub b/.dev-tools/stubs/OrderView.stub new file mode 100644 index 0000000..52f13d4 --- /dev/null +++ b/.dev-tools/stubs/OrderView.stub @@ -0,0 +1,169 @@ +_coreRegistry = $registry; + $this->_reorderHelper = $reorderHelper; + parent::__construct($context, $data); + } + + /** + * Retrieve order model object + * + * @return \Magento\Sales\Model\Order + */ + public function getOrder() {} + + /** + * Retrieve Order Identifier + * + * @return int + */ + public function getOrderId() {} + + /** + * Get URL for back button + * + * @return string + */ + public function getBackUrl() {} + + /** + * Get URL for edit button + * + * @return string + */ + public function getEditUrl() {} + + /** + * Get URL for email sending + * + * @return string + */ + public function getEmailUrl() {} + + /** + * Get URL for cancellation + * + * @return string + */ + public function getCancelUrl() {} + + /** + * Get URL for hold + * + * @return string + */ + public function getHoldUrl() {} + + /** + * Get URL for unhold + * + * @return string + */ + public function getUnholdUrl() {} + + /** + * Get URL for shipping + * + * @return string + */ + public function getShipUrl() {} + + /** + * Get URL for comment saving + * + * @return string + */ + public function getReviewPaymentUrl() {} +} + +namespace Magento\Backend\Block\Widget; + +/** + * Context class passed to Backend Widgets via Dependency Injection. + */ +class Context extends \Magento\Framework\View\Element\Template\Context +{ + /** + * @return \Magento\Framework\AuthorizationInterface + */ + public function getAuthorization() {} + + /** + * @return \Magento\Framework\Math\Random + */ + public function MathRandom() {} + + /** + * @return \Magento\Backend\Model\Session + */ + public function getBackendSession() {} + + /** + * @return \Magento\Backend\Model\UrlInterface + */ + public function getUrlBuilder() {} +} + +namespace Magento\Backend\Block\Widget; + +/** + * Base container abstract class for Adminhtml form and view containers. + */ +abstract class Container extends \Magento\Backend\Block\Widget +{ + /** + * Internal constructor + * + * @return void + */ + protected function _construct() {} + + /** + * Get back button URL + * + * @return string + */ + public function getBackUrl() {} + + /** + * Check whether button can be displayed + * + * @param string $id + * @return bool + */ + protected function _isAllowedAction($id) {} +} diff --git a/.version b/.version index 860487c..834f262 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.7.1 +2.8.0 diff --git a/CHANGELOG.MD b/CHANGELOG.MD index a26157d..c5dc702 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.8.0 + +### Added + +- New order status `pending_payment_tpay` assigned to newly created orders with Tpay payment methods. +- Ability to click "Pay with Tpay" in customer order details view (for orders in status `pending_payment_tpay`) +- Ability to send payment reminders from adminhtml order details view (for orders in status `pending_payment_tpay`) + ## 2.7.1 ### Added diff --git a/Controller/Adminhtml/Order/Reminder.php b/Controller/Adminhtml/Order/Reminder.php new file mode 100644 index 0000000..7716fb3 --- /dev/null +++ b/Controller/Adminhtml/Order/Reminder.php @@ -0,0 +1,79 @@ +reminderService = $reminderService; + $this->orderRepository = $orderRepository; + } + + public function execute() + { + $order = $this->orderRepository->get($this->getRequest()->getParam('order_id')); + + if (TpayPayment::ORDER_STATUS_PENDING !== $order->getStatus()) { + $this->messageManager->addErrorMessage(__('Reminder allowed only for pending payments.')); + + return $this->redirectBack(); + } + + $payment = $order->getPayment(); + if (null === $payment) { + $this->messageManager->addErrorMessage(__('No payment found for this order.')); + + return $this->redirectBack(); + } + + $info = $payment->getAdditionalInformation(); + if (empty($info['transaction_url'])) { + $this->messageManager->addErrorMessage(__('Payment does not contain transaction url.')); + + return $this->redirectBack(); + } + + try { + if ($this->reminderService->reminder($order)) { + $this->messageManager->addSuccessMessage(__('Reminder has been sent.')); + + return $this->redirectBack(); + } + } catch (Throwable $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); + } + + $this->messageManager->addErrorMessage(__('Problem during sending reminder.')); + + return $this->redirectBack(); + } + + // phpcs:ignore + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Tpay_Magento2::send_reminder'); + } + + private function redirectBack() + { + return $this->resultRedirectFactory->create()->setPath('sales/order/view', ['order_id' => $this->getRequest()->getParam('order_id')]); + } +} diff --git a/Model/Config/Source/EmailTemplate.php b/Model/Config/Source/EmailTemplate.php new file mode 100644 index 0000000..9784a59 --- /dev/null +++ b/Model/Config/Source/EmailTemplate.php @@ -0,0 +1,26 @@ +getAuthorization()->isAllowed('Tpay_Magento2::send_reminder')) { + return; + } + $order = $subject->getOrder(); + if (null === $order) { + return; + } + if (TpayPayment::ORDER_STATUS_PENDING !== $order->getStatus()) { + return; + } + $payment = $order->getPayment(); + if (null === $payment) { + return; + } + $info = $payment->getAdditionalInformation(); + if (empty($info['transaction_url'])) { + return; + } + + $subject->addButton( + 'send_tpay_reminder', + [ + 'label' => __('Send Payment Reminder'), + 'onclick' => "setLocation('".$subject->getUrl('tpay/order/reminder')."')", + 'class' => 'secondary', + ] + ); + } +} diff --git a/Service/ReminderService.php b/Service/ReminderService.php new file mode 100644 index 0000000..f6244be --- /dev/null +++ b/Service/ReminderService.php @@ -0,0 +1,84 @@ +transportBuilder = $transportBuilder; + $this->inlineTranslation = $inlineTranslation; + $this->orderRepository = $orderRepository; + $this->scopeConfig = $scopeConfig; + } + + public function reminder(OrderInterface $order) + { + $payment = $order->getPayment(); + if (null === $payment) { + return false; + } + $info = $payment->getAdditionalInformation(); + if (null === $info) { + return false; + } + if (empty($info['transaction_url'])) { + return false; + } + $paymentUrl = $info['transaction_url']; + + $this->inlineTranslation->suspend(); + + $templateId = $this->scopeConfig->getValue('payment/tpaycom_magento2basic/sale_settings/payment_remind_email_template', ScopeInterface::SCOPE_STORE, $order->getStoreId()); + if (empty($templateId)) { + $templateId = 'payment_tpaycom_magento2basic_sale_settings_payment_remind_email_template'; + } + + $transport = $this->transportBuilder + ->setTemplateIdentifier($templateId) + ->setTemplateOptions([ + 'area' => Area::AREA_FRONTEND, + 'store' => $order->getStoreId(), + ]) + ->setTemplateVars([ + 'paymentUrl' => $paymentUrl, + 'order' => $order->getIncrementId(), + 'name' => $order->getCustomerName(), + ]) + ->setFromByScope('general', $order->getStoreId()) + ->addTo($order->getCustomerEmail(), $order->getCustomerName()) + ->getTransport(); + + $transport->sendMessage(); + $this->inlineTranslation->resume(); + + $order->addCommentToStatusHistory(__('Payment reminder sent')); + $this->orderRepository->save($order); + + return true; + } +} diff --git a/Service/TpayService.php b/Service/TpayService.php index 055b377..0d73cad 100644 --- a/Service/TpayService.php +++ b/Service/TpayService.php @@ -12,6 +12,7 @@ use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Sales\Model\Service\InvoiceService; use Tpay\Magento2\Helper\OrderResolver; +use Tpay\Magento2\Model\TpayPayment; class TpayService { @@ -48,7 +49,7 @@ public function setOrderStatePendingPayment(string $orderId, bool $sendEmail): O ->setBaseTotalPaid(0.00) ->setBaseTotalDue($order->getBaseGrandTotal()) ->setState(Order::STATE_PENDING_PAYMENT) - ->addStatusToHistory(true); + ->addStatusToHistory(TpayPayment::ORDER_STATUS_PENDING); $order->setSendEmail($sendEmail); $this->orderRepository->save($order); @@ -60,7 +61,7 @@ public function addCommentToHistory($orderId, $comment) { /** @var Order $order */ $order = $this->orderResolver->getOrderByIncrementId($orderId); - $order->addStatusToHistory($order->getState(), $comment); + $order->addStatusToHistory($order->getStatus(), $comment); $this->orderRepository->save($order); } diff --git a/Setup/Patch/Data/InstallOrderStatus.php b/Setup/Patch/Data/InstallOrderStatus.php new file mode 100644 index 0000000..9f1c0c5 --- /dev/null +++ b/Setup/Patch/Data/InstallOrderStatus.php @@ -0,0 +1,51 @@ +moduleDataSetup = $moduleDataSetup; + } + + public static function getDependencies() + { + return []; + } + + public function getAliases() + { + return []; + } + + public function apply() + { + $this->moduleDataSetup->getConnection()->insert( + $this->moduleDataSetup->getTable('sales_order_status'), + [ + 'status' => TpayPayment::ORDER_STATUS_PENDING, + 'label' => __('Pending Payment with Tpay'), + ] + ); + + $this->moduleDataSetup->getConnection()->insert( + $this->moduleDataSetup->getTable('sales_order_status_state'), + [ + 'status' => TpayPayment::ORDER_STATUS_PENDING, + 'state' => 'pending_payment', + 'is_default' => 0, + 'visible_on_front' => 1, + ] + ); + + return $this; + } +} diff --git a/ViewModel/Order/Button.php b/ViewModel/Order/Button.php new file mode 100644 index 0000000..bb62e8d --- /dev/null +++ b/ViewModel/Order/Button.php @@ -0,0 +1,44 @@ +registry = $registry; + } + + public function shouldShowButton(): bool + { + if (TpayPayment::ORDER_STATUS_PENDING !== $this->getOrder()->getStatus()) { + return false; + } + + return !empty($this->getPaymentUrl()); + } + + public function getPaymentUrl(): ?string + { + $payment = $this->getOrder()->getPayment(); + if (null === $payment) { + return null; + } + $info = $payment->getAdditionalInformation(); + + return $info['transaction_url'] ?? null; + } + + private function getOrder(): OrderInterface + { + return $this->registry->registry('current_order'); + } +} diff --git a/etc/acl.xml b/etc/acl.xml new file mode 100644 index 0000000..a281b44 --- /dev/null +++ b/etc/acl.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml new file mode 100644 index 0000000..159da90 --- /dev/null +++ b/etc/adminhtml/di.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml new file mode 100644 index 0000000..bdd0f79 --- /dev/null +++ b/etc/adminhtml/routes.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index c0b8f5e..9c96839 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -183,6 +183,11 @@ If you use other currencies visible on the website and pay in PLN, turn it on/off Magento\Config\Model\Config\Source\Yesno + + + \Tpay\Magento2\Model\Config\Source\EmailTemplate + Emails can be sent manually from order details view given order staus is Pending Payment Tpay + diff --git a/etc/email_templates.xml b/etc/email_templates.xml new file mode 100644 index 0000000..55bbbdd --- /dev/null +++ b/etc/email_templates.xml @@ -0,0 +1,10 @@ + + +