From e83692997077576852b863fcbafc90b8c460170f Mon Sep 17 00:00:00 2001 From: afterpayplugins <53248085+afterpayplugins@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:17:15 +1100 Subject: [PATCH] Afterpay Release v5.2.1 --- Api/Data/TokenInterface.php | 10 ++ Controller/Express/PlaceOrder.php | 7 +- Controller/Payment/Capture.php | 44 ++++--- Model/Checks/PaymentMethod.php | 2 +- Model/Checks/PaymentMethodInterface.php | 8 +- Model/Config.php | 6 +- Model/GraphQl/Resolver/AfterpayConfig.php | 2 + Model/Order/CreditMemo/OrdersRetriever.php | 114 +++++++----------- Model/Order/Payment/Auth/TokenSaver.php | 25 ++++ Model/Order/Payment/Auth/TokenValidator.php | 23 ++-- Model/Payment/Capture/PlaceOrderProcessor.php | 33 ++++- .../NotAllowedProductsProvider.php | 15 +-- Model/ResourceModel/Token.php | 41 +++++++ Model/ResourceModel/Token/Collection.php | 18 +++ Model/Token.php | 18 +++ Plugin/Quote/CheckoutManagement.php | 39 ++++++ Setup/Patch/Data/AdaptCapturedDiscounts.php | 6 +- Setup/Patch/Data/MigrateTokens.php | 73 +++++++++++ composer.json | 2 +- etc/db_schema.xml | 22 ++++ etc/db_schema_whitelist.json | 16 +++ etc/di.xml | 4 + etc/module.xml | 2 +- etc/schema.graphqls | 1 + .../frontend/web/js/view/container/cta/cta.js | 3 +- .../view/container/express-checkout/button.js | 49 ++++++-- 26 files changed, 453 insertions(+), 130 deletions(-) create mode 100644 Api/Data/TokenInterface.php create mode 100644 Model/Order/Payment/Auth/TokenSaver.php create mode 100644 Model/ResourceModel/Token.php create mode 100644 Model/ResourceModel/Token/Collection.php create mode 100644 Model/Token.php create mode 100644 Plugin/Quote/CheckoutManagement.php create mode 100644 Setup/Patch/Data/MigrateTokens.php create mode 100644 etc/db_schema.xml create mode 100644 etc/db_schema_whitelist.json diff --git a/Api/Data/TokenInterface.php b/Api/Data/TokenInterface.php new file mode 100644 index 0000000..0c7f4e9 --- /dev/null +++ b/Api/Data/TokenInterface.php @@ -0,0 +1,10 @@ +request = $request; $this->checkoutSession = $checkoutSession; @@ -36,6 +38,7 @@ public function __construct( $this->placeOrderProcessor = $placeOrderProcessor; $this->syncCheckoutDataCommand = $syncCheckoutDataCommand; $this->storeManager = $storeManager; + $this->messageManager = $messageManager; } public function execute() @@ -74,6 +77,8 @@ public function execute() ]); } + $this->messageManager->addSuccessMessage((string)__('Afterpay Transaction Completed.')); + return $jsonResult->setData(['redirectUrl' => $this->storeManager->getStore()->getUrl('checkout/onepage/success')]); } } diff --git a/Controller/Payment/Capture.php b/Controller/Payment/Capture.php index 4d94991..079e4db 100644 --- a/Controller/Payment/Capture.php +++ b/Controller/Payment/Capture.php @@ -2,25 +2,33 @@ namespace Afterpay\Afterpay\Controller\Payment; -class Capture implements \Magento\Framework\App\Action\HttpGetActionInterface +use Afterpay\Afterpay\Model\Payment\Capture\PlaceOrderProcessor; +use Magento\Checkout\Model\Session; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Message\ManagerInterface; +use Magento\Payment\Gateway\CommandInterface; + +class Capture implements HttpGetActionInterface { const CHECKOUT_STATUS_CANCELLED = 'CANCELLED'; const CHECKOUT_STATUS_SUCCESS = 'SUCCESS'; - - private \Magento\Framework\App\RequestInterface $request; - private \Magento\Checkout\Model\Session $session; - private \Magento\Framework\Controller\Result\RedirectFactory $redirectFactory; - private \Magento\Framework\Message\ManagerInterface $messageManager; - private \Afterpay\Afterpay\Model\Payment\Capture\PlaceOrderProcessor $placeOrderProcessor; - private \Magento\Payment\Gateway\CommandInterface $validateCheckoutDataCommand; + private RequestInterface $request; + private Session $session; + private RedirectFactory $redirectFactory; + private ManagerInterface $messageManager; + private PlaceOrderProcessor $placeOrderProcessor; + private CommandInterface $validateCheckoutDataCommand; public function __construct( - \Magento\Framework\App\RequestInterface $request, - \Magento\Checkout\Model\Session $session, - \Magento\Framework\Controller\Result\RedirectFactory $redirectFactory, - \Magento\Framework\Message\ManagerInterface $messageManager, - \Afterpay\Afterpay\Model\Payment\Capture\PlaceOrderProcessor $placeOrderProcessor, - \Magento\Payment\Gateway\CommandInterface $validateCheckoutDataCommand + RequestInterface $request, + Session $session, + RedirectFactory $redirectFactory, + ManagerInterface $messageManager, + PlaceOrderProcessor $placeOrderProcessor, + CommandInterface $validateCheckoutDataCommand ) { $this->request = $request; $this->session = $session; @@ -36,12 +44,14 @@ public function execute() $this->messageManager->addErrorMessage( (string)__('You have cancelled your Afterpay payment. Please select an alternative payment method.') ); + return $this->redirectFactory->create()->setPath('checkout/cart'); } if ($this->request->getParam('status') != self::CHECKOUT_STATUS_SUCCESS) { $this->messageManager->addErrorMessage( (string)__('Afterpay payment is declined. Please select an alternative payment method.') ); + return $this->redirectFactory->create()->setPath('checkout/cart'); } @@ -50,14 +60,16 @@ public function execute() $afterpayOrderToken = $this->request->getParam('orderToken'); $this->placeOrderProcessor->execute($quote, $this->validateCheckoutDataCommand, $afterpayOrderToken); } catch (\Throwable $e) { - $errorMessage = $e instanceof \Magento\Framework\Exception\LocalizedException + $errorMessage = $e instanceof LocalizedException ? $e->getMessage() : (string)__('Afterpay payment is declined. Please select an alternative payment method.'); $this->messageManager->addErrorMessage($errorMessage); + return $this->redirectFactory->create()->setPath('checkout/cart'); } - $this->messageManager->addSuccessMessage((string)__('Afterpay Transaction Completed')); + $this->messageManager->addSuccessMessage((string)__('Afterpay Transaction Completed.')); + return $this->redirectFactory->create()->setPath('checkout/onepage/success'); } } diff --git a/Model/Checks/PaymentMethod.php b/Model/Checks/PaymentMethod.php index 5dc328e..94c5cff 100644 --- a/Model/Checks/PaymentMethod.php +++ b/Model/Checks/PaymentMethod.php @@ -4,7 +4,7 @@ class PaymentMethod implements PaymentMethodInterface { - public function isAfterPayMethod(\Magento\Sales\Model\Order\Payment $payment): bool + public function isAfterPayMethod($payment): bool { return $payment->getMethod() == \Afterpay\Afterpay\Gateway\Config\Config::CODE; } diff --git a/Model/Checks/PaymentMethodInterface.php b/Model/Checks/PaymentMethodInterface.php index d52399f..ab3a22d 100644 --- a/Model/Checks/PaymentMethodInterface.php +++ b/Model/Checks/PaymentMethodInterface.php @@ -1,8 +1,12 @@ resourceConnection->getConnection(); - $coreConfigData = $connection->getTableName('core_config_data'); + $coreConfigData = $this->resourceConnection->getTableName('core_config_data'); $configsExistToCheck = array_merge( \Afterpay\Afterpay\Observer\Adminhtml\ConfigSaveAfter::AFTERPAY_CONFIGS, \Afterpay\Afterpay\Observer\Adminhtml\ConfigSaveAfter::CONFIGS_PATHS_TO_TRACK @@ -555,9 +555,9 @@ public function setConsumerLendingMinAmount(string $value, int $scopeId = 0): se return $this; } - public function getConsumerLendingMinAmount(?int $scopeCode = null): bool + public function getConsumerLendingMinAmount(?int $scopeCode = null): float { - return (bool)$this->scopeConfig->getValue( + return (float)$this->scopeConfig->getValue( self::XML_PATH_CONSUMER_LENDING_MIN_AMOUNT, ScopeInterface::SCOPE_WEBSITE, $scopeCode diff --git a/Model/GraphQl/Resolver/AfterpayConfig.php b/Model/GraphQl/Resolver/AfterpayConfig.php index 85a9611..8e8845d 100644 --- a/Model/GraphQl/Resolver/AfterpayConfig.php +++ b/Model/GraphQl/Resolver/AfterpayConfig.php @@ -39,6 +39,7 @@ public function resolve( $isEnabledCtaMinicart = $this->config->getIsEnableCtaMiniCart((int)$websiteId); $isEnabledCtaCheckout = $this->config->getIsEnableCtaCartPage((int)$websiteId); $publicId = $this->config->getPublicId((int)$websiteId); + $apiMode = $this->config->getApiMode((int)$websiteId); return [ 'max_amount' => $maxAmount, @@ -48,6 +49,7 @@ public function resolve( 'is_enabled_cta_pdp' => $isEnabledCtaProductPage, 'is_enabled_cta_minicart' => $isEnabledCtaMinicart, 'is_enabled_cta_checkout' => $isEnabledCtaCheckout, + 'api_mode' => $apiMode, 'mpid' => $publicId, ]; } diff --git a/Model/Order/CreditMemo/OrdersRetriever.php b/Model/Order/CreditMemo/OrdersRetriever.php index e55090b..be981f1 100644 --- a/Model/Order/CreditMemo/OrdersRetriever.php +++ b/Model/Order/CreditMemo/OrdersRetriever.php @@ -2,100 +2,76 @@ namespace Afterpay\Afterpay\Model\Order\CreditMemo; -use Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface; +use Afterpay\Afterpay\Api\Data\TokenInterface; +use Afterpay\Afterpay\Model\ResourceModel\Token\CollectionFactory; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderPaymentInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\ResourceModel\Order\Collection; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory as OrderCollectionFactory; class OrdersRetriever { - private \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory; - private \Magento\Framework\App\ResourceConnection $resourceConnection; - private \Magento\Framework\Serialize\SerializerInterface $serializer; - private \Psr\Log\LoggerInterface $logger; + private OrderCollectionFactory $orderCollectionFactory; + private ResourceConnection $resourceConnection; + private CollectionFactory $tokensCollectionFactory; + private DateTime $dateTime; public function __construct( - \Magento\Framework\Serialize\SerializerInterface $serializer, - \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory, - \Magento\Framework\App\ResourceConnection $resourceConnection, - \Psr\Log\LoggerInterface $logger + OrderCollectionFactory $orderCollectionFactory, + ResourceConnection $resourceConnection, + CollectionFactory $tokensCollectionFactory, + DateTime $dateTime ) { - $this->serializer = $serializer; $this->orderCollectionFactory = $orderCollectionFactory; $this->resourceConnection = $resourceConnection; - $this->logger = $logger; + $this->tokensCollectionFactory = $tokensCollectionFactory; + $this->dateTime = $dateTime; } /** - * @return \Magento\Sales\Model\Order[] + * @return Order[] */ public function getAfterpayOrders(): array { - $orderCollection = $this->orderCollectionFactory->create(); - $orderCollection + $tokensCollection = $this->tokensCollectionFactory->create() + ->addFieldToSelect(TokenInterface::ORDER_ID_FIELD) + ->addFieldToFilter(TokenInterface::EXPIRATION_DATE_FIELD, ['notnull' => true]) ->addFieldToFilter( - 'state', - ['eq' => \Magento\Sales\Model\Order::STATE_PROCESSING] + TokenInterface::EXPIRATION_DATE_FIELD, + [ + 'date' => true, + 'from' => $this->dateTime->date('Y-m-d H:i:s', '-90 days'), + 'to' => $this->dateTime->date('Y-m-d H:i:s') + ] ); + $ids = $tokensCollection->getColumnValues(TokenInterface::ORDER_ID_FIELD); + + $orderCollection = $this->orderCollectionFactory->create(); + $orderCollection->addFieldToFilter( + OrderInterface::ENTITY_ID, + ['in' => $ids] + )->addFieldToFilter( + OrderInterface::STATE, + ['eq' => Order::STATE_PROCESSING] + ); $orderCollection = $this->joinAfterpayPaymentAdditionalInfo($orderCollection); - /** @var \Magento\Sales\Model\Order[] $items */ - $items = $orderCollection->getItems(); - $items = $this->getItemsWithAdditionalInfo($items); - return $items; - } - /** - * @var \Magento\Sales\Model\Order[] $items - * @return \Magento\Sales\Model\Order[] - */ - private function getItemsWithAdditionalInfo(array $items): array - { - $itemsWithJsonAdditionalInfo = []; - foreach ($items as $item) { - $additionalInformation = $item->getData( - \Magento\Sales\Api\Data\OrderPaymentInterface::ADDITIONAL_INFORMATION - ); - try { - $unserializedInfo = $this->serializer->unserialize($additionalInformation); - if (!is_array($unserializedInfo)) { - continue; - } - /** @var array $unserializedInfo */ - $item->setData( - \Magento\Sales\Api\Data\OrderPaymentInterface::ADDITIONAL_INFORMATION, - $unserializedInfo - ); - $isAdditionalInfoFull = - isset($unserializedInfo[AdditionalInformationInterface::AFTERPAY_PAYMENT_STATE]) && - isset($unserializedInfo[AdditionalInformationInterface::AFTERPAY_OPEN_TO_CAPTURE_AMOUNT]) && - isset($unserializedInfo[AdditionalInformationInterface::AFTERPAY_AUTH_EXPIRY_DATE]); - if ($isAdditionalInfoFull) { - $itemsWithJsonAdditionalInfo[] = $item; - } - } catch (\InvalidArgumentException $e) { - $this->logger->warning($e->getMessage()); - } - } - return $itemsWithJsonAdditionalInfo; + return $orderCollection->getItems(); } private function joinAfterpayPaymentAdditionalInfo( - \Magento\Sales\Model\ResourceModel\Order\Collection $orderCollection - ): \Magento\Sales\Model\ResourceModel\Order\Collection { - $salesOrderPaymentTable = $this->resourceConnection->getConnection()->getTableName('sales_order_payment'); + Collection $orderCollection + ): Collection { + $salesOrderPaymentTable = $this->resourceConnection->getTableName('sales_order_payment'); $orderCollection->join( ['sop' => $salesOrderPaymentTable], 'sop.parent_id = main_table.entity_id', - \Magento\Sales\Api\Data\OrderPaymentInterface::ADDITIONAL_INFORMATION + OrderPaymentInterface::ADDITIONAL_INFORMATION ); - $selectSql = $orderCollection->getSelectSql(); - /** @var \Magento\Framework\DB\Select $selectSql */ - $selectSql - ->where( - 'sop.method = ?', - \Afterpay\Afterpay\Gateway\Config\Config::CODE - ) - ->where( - 'sop.additional_information like ?', - '%' . AdditionalInformationInterface::AFTERPAY_AUTH_EXPIRY_DATE . '%' - ); + return $orderCollection; } } diff --git a/Model/Order/Payment/Auth/TokenSaver.php b/Model/Order/Payment/Auth/TokenSaver.php new file mode 100644 index 0000000..1c59e17 --- /dev/null +++ b/Model/Order/Payment/Auth/TokenSaver.php @@ -0,0 +1,25 @@ +tokensResource = $tokensResource; + $this->dateTime = $dateTime; + } + + public function execute(int $orderId, string $token, ?string $expiryDate): bool + { + return (bool)$this->tokensResource->insertNewToken($orderId, $token, $this->dateTime->formatDate($expiryDate)); + } +} diff --git a/Model/Order/Payment/Auth/TokenValidator.php b/Model/Order/Payment/Auth/TokenValidator.php index 743b58a..60b6c8c 100644 --- a/Model/Order/Payment/Auth/TokenValidator.php +++ b/Model/Order/Payment/Auth/TokenValidator.php @@ -2,29 +2,24 @@ namespace Afterpay\Afterpay\Model\Order\Payment\Auth; -use Afterpay\Afterpay\Api\Data\CheckoutInterface; -use Afterpay\Afterpay\Gateway\Config\Config; -use Magento\Framework\App\ResourceConnection; +use Afterpay\Afterpay\Model\ResourceModel\Token; class TokenValidator { - private ResourceConnection $resourceConnection; + private array $results = []; + private Token $tokensResource; - public function __construct(ResourceConnection $resourceConnection) + public function __construct(Token $tokensResource) { - $this->resourceConnection = $resourceConnection; + $this->tokensResource = $tokensResource; } public function checkIsUsed(string $token): bool { - $salesOrderPaymentTable = $this->resourceConnection->getConnection()->getTableName('sales_order_payment'); - $checkSelect = $this->resourceConnection->getConnection()->select() - ->from($salesOrderPaymentTable) - ->where('method = ?', Config::CODE) - ->where('base_amount_paid_online IS NOT NULL') - ->where('last_trans_id IS NOT NULL') - ->where('additional_information like ?', '%"' . CheckoutInterface::AFTERPAY_TOKEN . '":"' . $token . '%'); + if (!isset($this->results[$token])) { + $this->results[$token] = (bool)$this->tokensResource->selectByToken($token); + } - return (bool)$this->resourceConnection->getConnection()->fetchOne($checkSelect); + return $this->results[$token]; } } diff --git a/Model/Payment/Capture/PlaceOrderProcessor.php b/Model/Payment/Capture/PlaceOrderProcessor.php index 9aa400c..42cab1f 100644 --- a/Model/Payment/Capture/PlaceOrderProcessor.php +++ b/Model/Payment/Capture/PlaceOrderProcessor.php @@ -4,13 +4,17 @@ use Afterpay\Afterpay\Api\Data\CheckoutInterface; use Afterpay\Afterpay\Model\CBT\CheckCBTCurrencyAvailabilityInterface; +use Afterpay\Afterpay\Model\Order\Payment\Auth\TokenSaver; use Afterpay\Afterpay\Model\Order\Payment\Auth\TokenValidator; +use Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface; use Afterpay\Afterpay\Model\Payment\PaymentErrorProcessor; +use Magento\Checkout\Model\Session; use Magento\Customer\Api\Data\GroupInterface; use Magento\Payment\Gateway\CommandInterface; use Magento\Payment\Gateway\Data\PaymentDataObjectFactoryInterface; use Magento\Quote\Api\CartManagementInterface; use Magento\Quote\Model\Quote; +use Magento\Sales\Api\OrderRepositoryInterface; class PlaceOrderProcessor { @@ -19,19 +23,28 @@ class PlaceOrderProcessor private CheckCBTCurrencyAvailabilityInterface $checkCBTCurrencyAvailability; private PaymentErrorProcessor $paymentErrorProcessor; private TokenValidator $tokenValidator; + private TokenSaver $tokenSaver; + private OrderRepositoryInterface $orderRepository; + private Session $checkoutSession; public function __construct( CartManagementInterface $cartManagement, PaymentDataObjectFactoryInterface $paymentDataObjectFactory, CheckCBTCurrencyAvailabilityInterface $checkCBTCurrencyAvailability, PaymentErrorProcessor $paymentErrorProcessor, - TokenValidator $tokenValidator + TokenValidator $tokenValidator, + TokenSaver $tokenSaver, + OrderRepositoryInterface $orderRepository, + Session $checkoutSession ) { $this->cartManagement = $cartManagement; $this->paymentDataObjectFactory = $paymentDataObjectFactory; $this->checkCBTCurrencyAvailability = $checkCBTCurrencyAvailability; $this->paymentErrorProcessor = $paymentErrorProcessor; $this->tokenValidator = $tokenValidator; + $this->tokenSaver = $tokenSaver; + $this->orderRepository = $orderRepository; + $this->checkoutSession = $checkoutSession; } public function execute(Quote $quote, CommandInterface $checkoutDataCommand, string $afterpayOrderToken): int @@ -54,9 +67,23 @@ public function execute(Quote $quote, CommandInterface $checkoutDataCommand, str } $checkoutDataCommand->execute(['payment' => $this->paymentDataObjectFactory->create($payment)]); - return (int)$this->cartManagement->placeOrder($quote->getId()); + $this->checkoutSession->setAfterpayRedirect(true); + + $orderId = (int)$this->cartManagement->placeOrder($quote->getId()); } catch (\Throwable $e) { - return $this->paymentErrorProcessor->execute($quote, $e, $payment); + $orderId = $this->paymentErrorProcessor->execute($quote, $e, $payment); } + + $order = $this->orderRepository->get($orderId); + /** @var \Magento\Payment\Model\InfoInterface $orderPayment */ + $orderPayment = $order->getPayment(); + $this->tokenSaver->execute( + $orderId, + $afterpayOrderToken, + $orderPayment->getAdditionalInformation(AdditionalInformationInterface::AFTERPAY_AUTH_EXPIRY_DATE) + ); + $this->checkoutSession->setAfterpayRedirect(false); + + return $orderId; } } diff --git a/Model/ResourceModel/NotAllowedProductsProvider.php b/Model/ResourceModel/NotAllowedProductsProvider.php index 2cdb0cd..147f12b 100644 --- a/Model/ResourceModel/NotAllowedProductsProvider.php +++ b/Model/ResourceModel/NotAllowedProductsProvider.php @@ -5,14 +5,14 @@ class NotAllowedProductsProvider { private \Afterpay\Afterpay\Model\Config $config; - private \Magento\Framework\DB\Adapter\AdapterInterface $connection; + private \Magento\Framework\App\ResourceConnection $resourceConnection; public function __construct( - \Afterpay\Afterpay\Model\Config $config, + \Afterpay\Afterpay\Model\Config $config, \Magento\Framework\App\ResourceConnection $resourceConnection ) { $this->config = $config; - $this->connection = $resourceConnection->getConnection(); + $this->resourceConnection = $resourceConnection; } public function provideIds(?int $storeId = null): array @@ -22,11 +22,12 @@ public function provideIds(?int $storeId = null): array return []; } - $select = $this->connection->select()->from( - ['cat' => $this->connection->getTableName('catalog_category_product')], + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select()->from( + ['cat' => $this->resourceConnection->getTableName('catalog_category_product')], 'cat.product_id' - )->where($this->connection->prepareSqlCondition('cat.category_id', ['in' => $excludedCategoriesIds])); + )->where($connection->prepareSqlCondition('cat.category_id', ['in' => $excludedCategoriesIds])); - return $this->connection->fetchCol($select); + return $connection->fetchCol($select); } } diff --git a/Model/ResourceModel/Token.php b/Model/ResourceModel/Token.php new file mode 100644 index 0000000..a7353b6 --- /dev/null +++ b/Model/ResourceModel/Token.php @@ -0,0 +1,41 @@ +_init('afterpay_tokens_log', TokenInterface::LOG_ID_FIELD); + $this->_useIsObjectNew = true; + } + + public function selectByToken(string $token): string + { + $connection = $this->getConnection(); + $select = $connection->select() + ->from($this->getMainTable()) + ->where(TokenInterface::TOKEN_FIELD . ' = ?', $token); + + $result = $connection->fetchOne($select); + + return is_string($result) ? $result : ''; + } + + public function insertNewToken(int $orderId, string $token, ?string $expiryDate): int + { + return $this->getConnection()->insert( + $this->getMainTable(), + [ + TokenInterface::ORDER_ID_FIELD => $orderId, + TokenInterface::TOKEN_FIELD => $token, + TokenInterface::EXPIRATION_DATE_FIELD => $expiryDate, + ]); + } +} diff --git a/Model/ResourceModel/Token/Collection.php b/Model/ResourceModel/Token/Collection.php new file mode 100644 index 0000000..4e7f410 --- /dev/null +++ b/Model/ResourceModel/Token/Collection.php @@ -0,0 +1,18 @@ +_init(Model::class, ResourceModel::class); + } +} diff --git a/Model/Token.php b/Model/Token.php new file mode 100644 index 0000000..3a2c5cb --- /dev/null +++ b/Model/Token.php @@ -0,0 +1,18 @@ +_init(ResourceModel::class); + } +} diff --git a/Plugin/Quote/CheckoutManagement.php b/Plugin/Quote/CheckoutManagement.php new file mode 100644 index 0000000..875d801 --- /dev/null +++ b/Plugin/Quote/CheckoutManagement.php @@ -0,0 +1,39 @@ +quoteRepository = $quoteRepository; + $this->checkoutSession = $checkoutSession; + $this->paymentMethodChecker = $paymentMethodChecker; + } + + public function beforePlaceOrder(CartManagementInterface $subject, $cartId, PaymentInterface $paymentMethod = null) + { + $quote = $this->quoteRepository->getActive($cartId); + $payment = $quote->getPayment(); + if ($this->paymentMethodChecker->isAfterPayMethod($payment) && !$this->checkoutSession->getAfterpayRedirect()) { + throw new LocalizedException(__('You cannot use the chosen payment method.')); + } + + return [$cartId, $paymentMethod]; + } +} diff --git a/Setup/Patch/Data/AdaptCapturedDiscounts.php b/Setup/Patch/Data/AdaptCapturedDiscounts.php index b98b617..293bb02 100644 --- a/Setup/Patch/Data/AdaptCapturedDiscounts.php +++ b/Setup/Patch/Data/AdaptCapturedDiscounts.php @@ -51,7 +51,7 @@ private function saveOrdersAdditionalInfo(array $ordersAdditionalInfo): void { foreach ($ordersAdditionalInfo as $orderId => $additionalInfo) { $this->salesSetup->getConnection()->update( - $this->salesSetup->getConnection()->getTableName('sales_order_payment'), + $this->salesSetup->getTable('sales_order_payment'), ['additional_information' => $this->json->serialize($additionalInfo)], ['parent_id = ?' => $orderId] ); @@ -80,10 +80,10 @@ private function getAfterpayLegacyPaymentsInfo(): array $connection = $this->salesSetup->getConnection(); $select = $connection->select() ->from( - ['si' => $connection->getTableName('sales_invoice')], + ['si' => $this->salesSetup->getTable('sales_invoice')], ['si.order_id', 'si.base_customer_balance_amount', 'si.base_gift_cards_amount'] )->joinInner( - ['sop' => $connection->getTableName('sales_order_payment')], + ['sop' => $this->salesSetup->getTable('sales_order_payment')], 'si.order_id = sop.parent_id AND sop.method = "' . Config::CODE . '"' . ' AND sop.additional_information NOT LIKE "%' . AdditionalInformationInterface::AFTERPAY_CAPTURED_DISCOUNT . '%"', ['sop.additional_information'] diff --git a/Setup/Patch/Data/MigrateTokens.php b/Setup/Patch/Data/MigrateTokens.php new file mode 100644 index 0000000..37c06a7 --- /dev/null +++ b/Setup/Patch/Data/MigrateTokens.php @@ -0,0 +1,73 @@ +tokensResource = $tokensResource; + $this->serializer = $serializer; + $this->dateTime = $dateTime; + } + + public function getAliases(): array + { + return []; + } + + public static function getDependencies(): array + { + return [AdaptPayments::class]; + } + + public function apply(): self + { + $paymentsSelect = $this->tokensResource->getConnection() + ->select() + ->from($this->tokensResource->getTable('sales_order_payment')) + ->where(OrderPaymentInterface::METHOD . ' = ?', $this->paymentCode); + + $payments = $this->tokensResource->getConnection()->fetchAll($paymentsSelect); + $tokenEntries = []; + foreach ($payments as $payment) { + if (!empty($payment[OrderPaymentInterface::ADDITIONAL_INFORMATION])) { + $additionalInfo = $this->serializer->unserialize($payment[OrderPaymentInterface::ADDITIONAL_INFORMATION]); + $token = $additionalInfo[CheckoutInterface::AFTERPAY_TOKEN]; + $expiration = $additionalInfo[AdditionalInformationInterface::AFTERPAY_AUTH_EXPIRY_DATE] ?? null; + if ($expiration) { + $expiration = $this->dateTime->formatDate($expiration); + } + $tokenEntries[] = [ + TokenInterface::ORDER_ID_FIELD => $payment['parent_id'], + TokenInterface::TOKEN_FIELD => $token, + TokenInterface::EXPIRATION_DATE_FIELD => $expiration + ]; + } + } + + if (!empty($tokenEntries)) { + $this->tokensResource->getConnection()->insertOnDuplicate($this->tokensResource->getMainTable(), $tokenEntries); + } + + return $this; + } +} diff --git a/composer.json b/composer.json index aeb9f26..be90132 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "license": "Apache-2.0", "type": "magento2-module", "description": "Magento 2 Afterpay Payment Module", - "version": "5.2.0", + "version": "5.2.1", "require": { "php": ">=7.4.0", "magento/framework": "^103.0", diff --git a/etc/db_schema.xml b/etc/db_schema.xml new file mode 100644 index 0000000..9ed21a6 --- /dev/null +++ b/etc/db_schema.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + +
+
diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json new file mode 100644 index 0000000..0dd3ab8 --- /dev/null +++ b/etc/db_schema_whitelist.json @@ -0,0 +1,16 @@ +{ + "afterpay_tokens_log": { + "column": { + "log_id": true, + "order_id": true, + "token": true, + "expiration_date": true + }, + "constraint": { + "PRIMARY": true, + "AFTERPAY_TOKENS_LOG_ORDER_ID_SALES_ORDER_ENTITY_ID": true, + "AFTERPAY_TOKENS_LOG_TOKEN": true, + "AFTERPAY_TOKENS_LOG_ORDER_ID": true + } + } +} \ No newline at end of file diff --git a/etc/di.xml b/etc/di.xml index 528ea6f..20d5999 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -8,6 +8,7 @@ + @@ -390,4 +391,7 @@ Afterpay\Afterpay\Logger + + + diff --git a/etc/module.xml b/etc/module.xml index 549f040..d279b42 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,7 +1,7 @@ - + diff --git a/etc/schema.graphqls b/etc/schema.graphqls index 0df4795..a169ba2 100644 --- a/etc/schema.graphqls +++ b/etc/schema.graphqls @@ -38,5 +38,6 @@ type afterpayConfigOutput { is_enabled_cta_pdp: Boolean is_enabled_cta_minicart: Boolean is_enabled_cta_checkout: Boolean + api_mode: String mpid: String } diff --git a/view/frontend/web/js/view/container/cta/cta.js b/view/frontend/web/js/view/container/cta/cta.js index 459e3e4..ae05c9a 100644 --- a/view/frontend/web/js/view/container/cta/cta.js +++ b/view/frontend/web/js/view/container/cta/cta.js @@ -9,7 +9,8 @@ return Component.extend({ defaults: { dataIsEligible: "true", - dataCbtEnabledString: "false" + dataCbtEnabledString: "false", + pageType: "product" }, initialize: function () { const res = this._super(); diff --git a/view/frontend/web/js/view/container/express-checkout/button.js b/view/frontend/web/js/view/container/express-checkout/button.js index 0e95d87..0ed6127 100644 --- a/view/frontend/web/js/view/container/express-checkout/button.js +++ b/view/frontend/web/js/view/container/express-checkout/button.js @@ -36,13 +36,7 @@ define([ ); let errorMessage = $.localStorage.get('express-error-message'); if (errorMessage) { - customerData.set('messages', { - messages: [{ - type: 'error', - text: $t(errorMessage) - }] - }); - $.localStorage.remove('express-error-message'); + this._handleError(errorMessage); } return res; }, @@ -103,7 +97,7 @@ define([ }); } }, - _fail: function(actions, afterpayConst) { + _fail: function (actions, afterpayConst) { actions.reject(afterpayConst); AfterPay.close(); }, @@ -117,6 +111,45 @@ define([ return (this.countryCode && window.AfterPay !== undefined && this.isProductAllowed() && !(this.currentPrice() > floatMaxOrderTotal || this.currentPrice() < floatMinOrderTotal) && !this._getIsVirtual()) && this._super(); + }, + _handleError: function (errorMessage) { + $(document).ready(function () { + setTimeout(function () { + $('.message.error').fadeOut('slow'); + }, 10000000); + + var notNeedUpdateCount = 0; + var needUpdateCheck = setInterval(function () { + if (notNeedUpdateCount === 40) { // ~10s + clearInterval(needUpdateCheck); + } + let messages = customerData.get('messages'); + let newMessages = []; + let needUpdate = true; + $.each(messages().messages, function (key, value) { + if (value.type === 'error' && value.text === errorMessage) { + needUpdate = false; + return false; + } + newMessages.push({ + type: value.type, + text: value.text + }); + }); + + if (needUpdate) { + newMessages.push({ + type: 'error', + text: $t(errorMessage) + }); + customerData.set('messages', {messages: newMessages}); + } else { + notNeedUpdateCount++; + } + }, 250); + + $.localStorage.remove('express-error-message'); + }); } }); });