diff --git a/Block/Adminhtml/System/Config/Form/Field/CBTAvailableCurrencies.php b/Block/Adminhtml/System/Config/Form/Field/CBTAvailableCurrencies.php
index 5aa2ecf..fb50036 100644
--- a/Block/Adminhtml/System/Config/Form/Field/CBTAvailableCurrencies.php
+++ b/Block/Adminhtml/System/Config/Form/Field/CBTAvailableCurrencies.php
@@ -4,8 +4,47 @@
namespace Afterpay\Afterpay\Block\Adminhtml\System\Config\Form\Field;
+use Magento\Backend\Block\Template\Context;
+use Magento\Framework\Serialize\SerializerInterface;
+use Psr\Log\LoggerInterface;
+
class CBTAvailableCurrencies extends \Magento\Config\Block\System\Config\Form\Field
{
+ private $serializer;
+ private $logger;
+
+ public function __construct(
+ LoggerInterface $logger,
+ SerializerInterface $serializer,
+ Context $context,
+ array $data = []
+ ) {
+ $this->serializer = $serializer;
+ $this->logger = $logger;
+ parent::__construct($context, $data);
+ }
+
+ protected function _renderValue(\Magento\Framework\Data\Form\Element\AbstractElement $element)
+ {
+ try {
+ $CbtAvailableCurrencies = $this->serializer->unserialize($element->getValue());
+ $newValue = '';
+ if (!$CbtAvailableCurrencies) {
+ return parent::_renderValue($element);
+ }
+
+ foreach ($CbtAvailableCurrencies as $currencyCode => $currency) {
+ $newValue .= $currencyCode . '(min:' . $currency['minimumAmount']['amount']
+ . ',max:' . $currency['maximumAmount']['amount'] . ') ';
+ }
+ $element->setValue($newValue);
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ }
+
+ return parent::_renderValue($element);
+ }
+
public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element)
{
/** @phpstan-ignore-next-line */
diff --git a/Controller/Express/PlaceOrder.php b/Controller/Express/PlaceOrder.php
index 7574e03..cdf44f4 100644
--- a/Controller/Express/PlaceOrder.php
+++ b/Controller/Express/PlaceOrder.php
@@ -2,10 +2,20 @@
namespace Afterpay\Afterpay\Controller\Express;
-class PlaceOrder implements \Magento\Framework\App\Action\HttpPostActionInterface
-{
- const CANCELLED_STATUS = 'CANCELLED';
+use Afterpay\Afterpay\Controller\Payment\Capture;
+use Afterpay\Afterpay\Gateway\Config\Config;
+use Afterpay\Afterpay\Model\Payment\Capture\PlaceOrderProcessor;
+use Magento\Checkout\Model\Session;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\App\RequestInterface;
+use Magento\Framework\Controller\Result\JsonFactory;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Message\ManagerInterface;
+use Magento\Framework\UrlInterface;
+use Magento\Payment\Gateway\CommandInterface;
+class PlaceOrder implements HttpPostActionInterface
+{
private $request;
private $messageManager;
private $checkoutSession;
@@ -15,13 +25,13 @@ class PlaceOrder implements \Magento\Framework\App\Action\HttpPostActionInterfac
private $syncCheckoutDataCommand;
public function __construct(
- \Magento\Framework\App\RequestInterface $request,
- \Magento\Framework\Message\ManagerInterface $messageManager,
- \Magento\Checkout\Model\Session $checkoutSession,
- \Magento\Framework\Controller\Result\JsonFactory $jsonFactory,
- \Magento\Framework\UrlInterface $url,
- \Afterpay\Afterpay\Model\Payment\Capture\PlaceOrderProcessor $placeOrderProcessor,
- \Magento\Payment\Gateway\CommandInterface $syncCheckoutDataCommand
+ RequestInterface $request,
+ ManagerInterface $messageManager,
+ Session $checkoutSession,
+ JsonFactory $jsonFactory,
+ UrlInterface $url,
+ PlaceOrderProcessor $placeOrderProcessor,
+ CommandInterface $syncCheckoutDataCommand
) {
$this->request = $request;
$this->messageManager = $messageManager;
@@ -40,25 +50,29 @@ public function execute()
$afterpayOrderToken = $this->request->getParam('orderToken');
$status = $this->request->getParam('status');
- if ($status === static::CANCELLED_STATUS) {
+ if ($status === Capture::CHECKOUT_STATUS_CANCELLED) {
return $jsonResult;
}
+ if ($status !== Capture::CHECKOUT_STATUS_SUCCESS) {
+ $errorMessage = (string)__('Afterpay payment is declined. Please select an alternative payment method.');
+ $this->messageManager->addErrorMessage($errorMessage);
+
+ return $jsonResult->setData(['redirectUrl' => $this->url->getUrl('checkout/cart')]);
+ }
+
try {
$quote->getPayment()
- ->setMethod(\Afterpay\Afterpay\Gateway\Config\Config::CODE)
+ ->setMethod(Config::CODE)
->setAdditionalInformation('afterpay_express', true);
$this->placeOrderProcessor->execute($quote, $this->syncCheckoutDataCommand, $afterpayOrderToken);
} catch (\Throwable $e) {
- $errorMessage = $e instanceof \Magento\Framework\Exception\LocalizedException
+ $errorMessage = $e instanceof LocalizedException
? $e->getMessage()
- : (string)__('Afterpay payment declined. Please select an alternative payment method.');
+ : (string)__('Afterpay payment is declined. Please select an alternative payment method.');
+ $this->messageManager->addErrorMessage($errorMessage);
- return $jsonResult->setData(['error' => $errorMessage, 'redirectUrl' => $this->url->getUrl(
- 'checkout/cart',
- ['_scope' => $quote->getStore()]
- )]
- );
+ return $jsonResult->setData(['redirectUrl' => $this->url->getUrl('checkout/cart')]);
}
return $jsonResult->setData(['redirectUrl' => $this->url->getUrl('checkout/onepage/success')]);
diff --git a/Gateway/Request/Checkout/CheckoutDataBuilder.php b/Gateway/Request/Checkout/CheckoutDataBuilder.php
index dc2ff6a..2a9d211 100644
--- a/Gateway/Request/Checkout/CheckoutDataBuilder.php
+++ b/Gateway/Request/Checkout/CheckoutDataBuilder.php
@@ -101,18 +101,26 @@ protected function getItems(\Magento\Quote\Model\Quote $quote): array
foreach ($quoteItems as $item) {
$productId = $item->getProduct()->getId();
+ $amount = $isCBTCurrencyAvailable ? $item->getPriceInclTax() : $item->getBasePriceInclTax();
+ $currencyCode = $isCBTCurrencyAvailable ? $quote->getQuoteCurrencyCode() : $quote->getBaseCurrencyCode();
+ $qty = $item->getQty();
+ $isIntQty = floor($qty) == $qty;
+ if ($isIntQty) {
+ $qty = (int)$item->getQty();
+ } else {
+ $amount *= $item->getQty();
+ $qty = 1;
+ }
$formattedItem = [
'name' => $item->getName(),
'sku' => $item->getSku(),
- 'quantity' => $item->getQty(),
+ 'quantity' => $qty,
'pageUrl' => $item->getProduct()->getProductUrl(),
'categories' => [array_values($this->getQuoteItemCategoriesNames($item))],
'price' => [
- 'amount' => $this->formatPrice(
- $isCBTCurrencyAvailable ? $item->getPriceInclTax() : $item->getBasePriceInclTax()
- ),
- 'currency' => $isCBTCurrencyAvailable ? $quote->getQuoteCurrencyCode() : $quote->getBaseCurrencyCode()
+ 'amount' => $this->formatPrice($amount),
+ 'currency' => $currencyCode
]
];
@@ -132,10 +140,7 @@ protected function getQuoteItemCategoriesNames(\Magento\Quote\Model\Quote\Item $
/** @var \Magento\Catalog\Model\ResourceModel\AbstractCollection $categoryCollection */
$categoryCollection = $item->getProduct()->getCategoryCollection();
$itemCategories = $categoryCollection->addAttributeToSelect('name')->getItems();
- return array_map(
- function ($cat) {return $cat->getData('name');},
- $itemCategories
- );
+ return array_map(static function ($cat) {return $cat->getData('name');}, $itemCategories);
}
/**
@@ -146,11 +151,8 @@ protected function getItemsImages(array $items): array
{
$itemsImages = [];
$searchCriteria = $this->searchCriteriaBuilder
- ->addFilter(
- 'entity_id',
- array_map(function ($item) {return $item->getProduct()->getId();}, $items),
- 'in'
- )->create();
+ ->addFilter('entity_id', array_map(static function ($item) {return $item->getProduct()->getId();}, $items), 'in')
+ ->create();
$products = $this->productRepository->getList($searchCriteria)->getItems();
foreach ($products as $product) {
@@ -184,13 +186,14 @@ protected function getShippingAmount(\Magento\Quote\Model\Quote $quote): ?array
return null;
}
$isCBTCurrencyAvailable = $this->checkCBTCurrencyAvailability->checkByQuote($quote);
+ $amount = $isCBTCurrencyAvailable
+ ? $quote->getShippingAddress()->getShippingAmount()
+ : $quote->getShippingAddress()->getBaseShippingAmount();
+ $currencyCode = $isCBTCurrencyAvailable ? $quote->getQuoteCurrencyCode() : $quote->getBaseCurrencyCode();
return [
- 'amount' => $this->formatPrice($isCBTCurrencyAvailable
- ? $quote->getShippingAddress()->getShippingAmount()
- : $quote->getShippingAddress()->getBaseShippingAmount()
- ),
- 'currency' => $isCBTCurrencyAvailable ? $quote->getQuoteCurrencyCode() : $quote->getBaseCurrencyCode()
+ 'amount' => $this->formatPrice($amount),
+ 'currency' => $currencyCode
];
}
@@ -211,13 +214,16 @@ protected function getDiscounts(\Magento\Quote\Model\Quote $quote): ?array
return null;
}
$isCBTCurrencyAvailable = $this->checkCBTCurrencyAvailability->checkByQuote($quote);
+ $amount = $isCBTCurrencyAvailable
+ ? $quote->getDiscountAmount()
+ : $quote->getBaseDiscountAmount();
+ $currencyCode = $isCBTCurrencyAvailable ? $quote->getQuoteCurrencyCode() : $quote->getBaseCurrencyCode();
+
return [
'displayName' => __('Discount'),
'amount' => [
- 'amount' => $this->formatPrice($isCBTCurrencyAvailable
- ? $quote->getDiscountAmount()
- : $quote->getBaseDiscountAmount()),
- 'currency' => $isCBTCurrencyAvailable ? $quote->getQuoteCurrencyCode() : $quote->getBaseCurrencyCode()
+ 'amount' => $this->formatPrice($amount),
+ 'currency' => $currencyCode
]
];
}
diff --git a/Gateway/Response/Checkout/CheckoutItemsAmountValidationHandler.php b/Gateway/Response/Checkout/CheckoutItemsAmountValidationHandler.php
index 6196156..30792d8 100644
--- a/Gateway/Response/Checkout/CheckoutItemsAmountValidationHandler.php
+++ b/Gateway/Response/Checkout/CheckoutItemsAmountValidationHandler.php
@@ -35,6 +35,14 @@ public function handle(array $handlingSubject, array $response)
throw new \Magento\Framework\Exception\LocalizedException($invalidCartItemsExceptionMessage);
}
if ($item->getQty() != $responseItems[$itemIndex]['quantity']) {
+ $qty = $item->getQty();
+ $isIntQty = floor($qty) == $qty;
+ if (!$isIntQty && $responseItems[$itemIndex]['quantity'] == 1) {
+ $amount = $isCBTCurrency ? $item->getPriceInclTax() : $item->getBasePriceInclTax();
+ if ($amount * $qty == $responseItems[$itemIndex]['price']['amount']) {
+ continue;
+ }
+ }
throw new \Magento\Framework\Exception\LocalizedException($invalidCartItemsExceptionMessage);
}
}
diff --git a/Gateway/Response/MerchantConfiguration/CBTAvailableCurrenciesConfigurationHandler.php b/Gateway/Response/MerchantConfiguration/CBTAvailableCurrenciesConfigurationHandler.php
index 617ac6d..e27148e 100644
--- a/Gateway/Response/MerchantConfiguration/CBTAvailableCurrenciesConfigurationHandler.php
+++ b/Gateway/Response/MerchantConfiguration/CBTAvailableCurrenciesConfigurationHandler.php
@@ -2,30 +2,35 @@
namespace Afterpay\Afterpay\Gateway\Response\MerchantConfiguration;
-class CBTAvailableCurrenciesConfigurationHandler implements \Magento\Payment\Gateway\Response\HandlerInterface
+use Afterpay\Afterpay\Model\Config;
+use Magento\Payment\Gateway\Response\HandlerInterface;
+use Magento\Framework\Serialize\SerializerInterface;
+
+class CBTAvailableCurrenciesConfigurationHandler implements HandlerInterface
{
private $config;
+ private $serializer;
public function __construct(
- \Afterpay\Afterpay\Model\Config $config
+ Config $config,
+ SerializerInterface $serializer
) {
$this->config = $config;
+ $this->serializer = $serializer;
}
public function handle(array $handlingSubject, array $response): void
{
$websiteId = (int)$handlingSubject['websiteId'];
- $cbtAvailableCurrencies = [];
+ $cbtAvailableCurrencies = '';
+
if (isset($response['CBT']['enabled']) &&
isset($response['CBT']['limits']) &&
is_array($response['CBT']['limits'])
) {
- foreach ($response['CBT']['limits'] as $limit) {
- if (isset($limit['maximumAmount']['currency']) && isset($limit['maximumAmount']['amount'])) {
- $cbtAvailableCurrencies[] = $limit['maximumAmount']['currency'] . ':' . $limit['maximumAmount']['amount'];
- }
- }
+ $cbtAvailableCurrencies = $this->serializer->serialize($response['CBT']['limits']);
}
- $this->config->setCbtCurrencyLimits(implode(",", $cbtAvailableCurrencies), $websiteId);
+
+ $this->config->setCbtCurrencyLimits($cbtAvailableCurrencies, $websiteId);
}
}
diff --git a/Gateway/Validator/Method/CurrencyValidator.php b/Gateway/Validator/Method/CurrencyValidator.php
index 3a635db..7d3cd98 100644
--- a/Gateway/Validator/Method/CurrencyValidator.php
+++ b/Gateway/Validator/Method/CurrencyValidator.php
@@ -20,7 +20,7 @@ public function __construct(
public function validate(array $validationSubject): \Magento\Payment\Gateway\Validator\ResultInterface
{
$quote = $this->checkoutSession->getQuote();
- $currentCurrency = $quote->getQuoteCurrencyCode();
+ $currentCurrency = $quote->getStore()->getCurrentCurrencyCode();
$allowedCurrencies = $this->config->getAllowedCurrencies();
$cbtCurrencies = array_keys($this->config->getCbtCurrencyLimits());
diff --git a/Model/Checks/IsCBTAvailable.php b/Model/Checks/IsCBTAvailable.php
index 942ab13..2b99b7f 100644
--- a/Model/Checks/IsCBTAvailable.php
+++ b/Model/Checks/IsCBTAvailable.php
@@ -30,7 +30,7 @@ public function checkByQuote(\Magento\Quote\Model\Quote $quote): bool
return $this->canUseCurrentCurrency;
}
- $currentCurrencyCode = $quote->getQuoteCurrencyCode();
+ $currentCurrencyCode = $quote->getQuoteCurrencyCode() ?? $quote->getStore()->getCurrentCurrencyCode();
$amount = (float) $quote->getGrandTotal();
$this->canUseCurrentCurrency = $this->check($currentCurrencyCode, $amount);
diff --git a/Model/Config.php b/Model/Config.php
index 1e16326..22be487 100644
--- a/Model/Config.php
+++ b/Model/Config.php
@@ -1,10 +1,12 @@
-scopeConfig = $scopeConfig;
$this->writer = $writer;
$this->resourceConnection = $resourceConnection;
+ $this->serializer = $serializer;
}
public function getIsPaymentActive(?int $scopeCode = null): bool
@@ -172,7 +178,6 @@ public function getMinOrderTotal(?int $scopeCode = null): ?string
public function getCbtCurrencyLimits(?int $scopeCode = null): array
{
- $data = [];
$value = $this->scopeConfig->getValue(
self::XML_PATH_CBT_CURRENCY_LIMITS,
ScopeInterface::SCOPE_WEBSITE,
@@ -183,15 +188,7 @@ public function getCbtCurrencyLimits(?int $scopeCode = null): array
return [];
}
- $list = explode(',', $value);
- foreach ($list as $item) {
- $currencyLimit = explode(':', $item);
- if (isset($currencyLimit[0]) && isset($currencyLimit[1])) {
- $data[$currencyLimit[0]] = (float) $currencyLimit[1];
- }
- }
-
- return $data;
+ return $this->serializer->unserialize($value);
}
public function getExcludeCategories(?int $scopeCode = null): array
@@ -205,6 +202,15 @@ public function getExcludeCategories(?int $scopeCode = null): array
return $excludeCategories ? explode(',', $excludeCategories) : [];
}
+ public function getIsReversalEnabled(?int $scopeCode = null): bool
+ {
+ return $this->scopeConfig->isSetFlag(
+ self::XML_PATH_ENABLE_REVERSAL,
+ ScopeInterface::SCOPE_WEBSITE,
+ $scopeCode
+ );
+ }
+
public function setMaxOrderTotal(string $value, int $scopeId = 0): self
{
if ($scopeId) {
@@ -347,7 +353,7 @@ public function setSpecificCountries(string $value, int $scopeId = 0): self
public function getMerchantCountry(
string $scope = ScopeInterface::SCOPE_WEBSITES,
- ?int $scopeCode = null
+ ?int $scopeCode = null
): ?string {
if ($countryCode = $this->scopeConfig->getValue(
self::XML_PATH_PAYPAL_MERCHANT_COUNTRY,
@@ -368,7 +374,7 @@ public function getMerchantCountry(
public function getMerchantCurrency(
string $scope = ScopeInterface::SCOPE_WEBSITES,
- ?int $scopeCode = null
+ ?int $scopeCode = null
): ?string {
return $this->scopeConfig->getValue(
\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE,
@@ -401,7 +407,7 @@ public function websiteHasOwnConfig(int $websiteId): bool
\Afterpay\Afterpay\Observer\Adminhtml\ConfigSaveAfter::AFTERPAY_CONFIGS,
\Afterpay\Afterpay\Observer\Adminhtml\ConfigSaveAfter::CONFIGS_PATHS_TO_TRACK
);
- $selectQuery = $connection->select()->from($coreConfigData, ['path','value'])
+ $selectQuery = $connection->select()->from($coreConfigData, ['path', 'value'])
->where("scope = ?", \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES)
->where("scope_id = ?", $websiteId)
->where("path in (?)", $configsExistToCheck);
diff --git a/Model/Config/CategorySourceRegistry.php b/Model/Config/CategorySourceRegistry.php
new file mode 100644
index 0000000..3d10fda
--- /dev/null
+++ b/Model/Config/CategorySourceRegistry.php
@@ -0,0 +1,18 @@
+showAllCategories;
+ }
+
+ public function setShowAllCategories(bool $value): void
+ {
+ $this->showAllCategories = $value;
+ }
+}
diff --git a/Model/Config/Source/Category.php b/Model/Config/Source/Category.php
index c5be0cb..7ab914b 100644
--- a/Model/Config/Source/Category.php
+++ b/Model/Config/Source/Category.php
@@ -7,15 +7,18 @@ class Category implements \Magento\Framework\Data\OptionSourceInterface
private $storeManager;
private $request;
private $categoryHelper;
+ private $categorySourceRegistry;
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\App\RequestInterface $request,
- \Magento\Catalog\Helper\Category $categoryHelper
+ \Magento\Catalog\Helper\Category $categoryHelper,
+ \Afterpay\Afterpay\Model\Config\CategorySourceRegistry $categorySourceRegistry
) {
$this->storeManager = $storeManager;
$this->request = $request;
$this->categoryHelper = $categoryHelper;
+ $this->categorySourceRegistry = $categorySourceRegistry;
}
public function toOptionArray(): array
@@ -45,8 +48,10 @@ private function getCategoriesTree(): array
$currentStoreId = $this->storeManager->getStore()->getId();
$this->storeManager->setCurrentStore($this->getStoreIdByRequest() ?? $currentStoreId);
+ $this->categorySourceRegistry->setShowAllCategories(true);
/** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categories */
$categories = $this->categoryHelper->getStoreCategories(false, true);
+ $this->categorySourceRegistry->setShowAllCategories(false);
$this->storeManager->setCurrentStore($currentStoreId);
return $this->convertToTree($categories);
diff --git a/Model/Order/Payment/Auth/TokenValidator.php b/Model/Order/Payment/Auth/TokenValidator.php
new file mode 100644
index 0000000..cc51b0b
--- /dev/null
+++ b/Model/Order/Payment/Auth/TokenValidator.php
@@ -0,0 +1,30 @@
+resourceConnection = $resourceConnection;
+ }
+
+ 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 . '%');
+
+ return (bool)$this->resourceConnection->getConnection()->fetchOne($checkSelect);
+ }
+}
diff --git a/Model/Payment/Capture/CancelOrderProcessor.php b/Model/Payment/Capture/CancelOrderProcessor.php
index ec07f18..9e6ca38 100644
--- a/Model/Payment/Capture/CancelOrderProcessor.php
+++ b/Model/Payment/Capture/CancelOrderProcessor.php
@@ -18,8 +18,7 @@ public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Afterpay\Afterpay\Model\Config $config,
\Afterpay\Afterpay\Model\Order\Payment\QuotePaidStorage $quotePaidStorage
- )
- {
+ ) {
$this->paymentDataObjectFactory = $paymentDataObjectFactory;
$this->reversalCommand = $reversalCommand;
$this->voidCommand = $voidCommand;
@@ -28,18 +27,23 @@ public function __construct(
$this->quotePaidStorage = $quotePaidStorage;
}
- /**
- * @throws \Magento\Payment\Gateway\Command\CommandException
- * @throws \Magento\Framework\Exception\LocalizedException
- */
public function execute(\Magento\Quote\Model\Quote\Payment $payment, int $quoteId): void
{
+ if (!$this->config->getIsReversalEnabled()) {
+ return;
+ }
+
$commandSubject = ['payment' => $this->paymentDataObjectFactory->create($payment)];
if (!$this->isDeferredPaymentFlow()) {
$this->reversalCommand->execute($commandSubject);
- return;
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __(
+ 'There was a problem placing your order. Your Afterpay order %1 is refunded.',
+ $payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_ORDER_ID)
+ )
+ );
}
$afterpayPayment = $this->quotePaidStorage->getAfterpayPaymentIfQuoteIsPaid($quoteId);
diff --git a/Model/Payment/Capture/PlaceOrderProcessor.php b/Model/Payment/Capture/PlaceOrderProcessor.php
index 81d51ae..49f6fc5 100644
--- a/Model/Payment/Capture/PlaceOrderProcessor.php
+++ b/Model/Payment/Capture/PlaceOrderProcessor.php
@@ -2,72 +2,62 @@
namespace Afterpay\Afterpay\Model\Payment\Capture;
-use Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface;
+use Afterpay\Afterpay\Api\Data\CheckoutInterface;
+use Afterpay\Afterpay\Model\CBT\CheckCBTCurrencyAvailabilityInterface;
+use Afterpay\Afterpay\Model\Order\Payment\Auth\TokenValidator;
+use Afterpay\Afterpay\Model\Payment\PaymentErrorProcessor;
+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;
class PlaceOrderProcessor
{
private $cartManagement;
- private $cancelOrderProcessor;
private $paymentDataObjectFactory;
private $checkCBTCurrencyAvailability;
- private $logger;
+ private $tokenValidator;
+ private $paymentErrorProcessor;
public function __construct(
- \Magento\Quote\Api\CartManagementInterface $cartManagement,
- \Afterpay\Afterpay\Model\Payment\Capture\CancelOrderProcessor $cancelOrderProcessor,
- \Magento\Payment\Gateway\Data\PaymentDataObjectFactoryInterface $paymentDataObjectFactory,
- \Afterpay\Afterpay\Model\CBT\CheckCBTCurrencyAvailabilityInterface $checkCBTCurrencyAvailability,
- \Psr\Log\LoggerInterface $logger
- )
- {
+ CartManagementInterface $cartManagement,
+ PaymentDataObjectFactoryInterface $paymentDataObjectFactory,
+ CheckCBTCurrencyAvailabilityInterface $checkCBTCurrencyAvailability,
+ TokenValidator $tokenValidator,
+ PaymentErrorProcessor $paymentErrorProcessor
+ ) {
$this->cartManagement = $cartManagement;
- $this->cancelOrderProcessor = $cancelOrderProcessor;
$this->paymentDataObjectFactory = $paymentDataObjectFactory;
$this->checkCBTCurrencyAvailability = $checkCBTCurrencyAvailability;
- $this->logger = $logger;
+ $this->tokenValidator = $tokenValidator;
+ $this->paymentErrorProcessor = $paymentErrorProcessor;
}
public function execute(Quote $quote, CommandInterface $checkoutDataCommand, string $afterpayOrderToken): void
{
+ if ($this->tokenValidator->checkIsUsed($afterpayOrderToken)) {
+ return;
+ }
+
+ $payment = $quote->getPayment();
try {
- $payment = $quote->getPayment();
- $payment->setAdditionalInformation(
- \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_TOKEN,
- $afterpayOrderToken
- );
+ $payment->setAdditionalInformation(CheckoutInterface::AFTERPAY_TOKEN, $afterpayOrderToken);
$isCBTCurrencyAvailable = $this->checkCBTCurrencyAvailability->checkByQuote($quote);
- $payment->setAdditionalInformation(
- \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY,
- $isCBTCurrencyAvailable
- );
- $payment->setAdditionalInformation(
- \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_CBT_CURRENCY,
- $quote->getQuoteCurrencyCode()
- );
+ $payment->setAdditionalInformation(CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY, $isCBTCurrencyAvailable);
+ $payment->setAdditionalInformation(CheckoutInterface::AFTERPAY_CBT_CURRENCY, $quote->getQuoteCurrencyCode());
if (!$quote->getCustomerId()) {
$quote->setCustomerEmail($quote->getBillingAddress()->getEmail())
->setCustomerIsGuest(true)
- ->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID);
+ ->setCustomerGroupId(GroupInterface::NOT_LOGGED_IN_ID);
}
$checkoutDataCommand->execute(['payment' => $this->paymentDataObjectFactory->create($payment)]);
-
$this->cartManagement->placeOrder($quote->getId());
} catch (\Throwable $e) {
- $this->logger->critical('Order placement is failed with error: ' . $e->getMessage());
- $quoteId = (int)$quote->getId();
- $this->cancelOrderProcessor->execute($payment, $quoteId);
-
- throw new \Magento\Framework\Exception\LocalizedException(
- __(
- '%1 payment declined. Please select an alternative payment method.',
- $quote->getPayment()->getMethodInstance()->getTitle()
- )
- );
+ $this->paymentErrorProcessor->execute($quote, $e, $payment);
}
}
}
diff --git a/Model/Payment/PaymentErrorProcessor.php b/Model/Payment/PaymentErrorProcessor.php
new file mode 100644
index 0000000..4feac5b
--- /dev/null
+++ b/Model/Payment/PaymentErrorProcessor.php
@@ -0,0 +1,58 @@
+checkoutSession = $checkoutSession;
+ $this->orderRepository = $orderRepository;
+ $this->cancelOrderProcessor = $cancelOrderProcessor;
+ $this->logger = $logger;
+ }
+
+ public function execute(Quote $quote, \Throwable $e, Payment $payment)
+ {
+ $this->logger->critical('Order placement is failed with error: ' . PHP_EOL . $e);
+ if (($this->checkoutSession->getLastSuccessQuoteId() == $quote->getId()) && $this->checkoutSession->getLastOrderId()) {
+ try {
+ $order = $this->orderRepository->get((int)$this->checkoutSession->getLastOrderId());
+ $order->addCommentToStatusHistory(
+ 'Afterpay detected a Magento exception during order creation. Please review:' . $e->getMessage(),
+ self::ORDER_STATUS_CODE,
+ false
+ );
+ $this->orderRepository->save($order);
+
+ return $order->getEntityId();
+ } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ }
+ }
+
+ $this->cancelOrderProcessor->execute($payment, (int)$quote->getId());
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __(
+ 'There was a problem placing your order. Please make sure your Afterpay %1 order has been refunded.',
+ $payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_ORDER_ID)
+ )
+ );
+ }
+}
diff --git a/Plugin/Catalog/Model/ResourceModel/Category/Tree.php b/Plugin/Catalog/Model/ResourceModel/Category/Tree.php
new file mode 100644
index 0000000..561464a
--- /dev/null
+++ b/Plugin/Catalog/Model/ResourceModel/Category/Tree.php
@@ -0,0 +1,30 @@
+categorySourceRegistry = $categorySourceRegistry;
+ }
+
+ public function beforeAddCollectionData(
+ \Magento\Catalog\Model\ResourceModel\Category\Tree $subject,
+ $collection = null,
+ $sorted = false,
+ $exclude = [],
+ $toLoad = true,
+ $onlyActive = false
+ ): array {
+ return [
+ $collection,
+ $sorted,
+ $exclude,
+ $toLoad,
+ $this->categorySourceRegistry->getShowAllCategories() ? false : $onlyActive
+ ];
+ }
+}
diff --git a/Plugin/Order/Payment/State/CaptureCommand.php b/Plugin/Order/Payment/State/CaptureCommand.php
new file mode 100644
index 0000000..c52b6f1
--- /dev/null
+++ b/Plugin/Order/Payment/State/CaptureCommand.php
@@ -0,0 +1,68 @@
+statusResolver = $statusResolver;
+ $this->config = $config;
+ }
+
+ public function aroundExecute(
+ \Magento\Sales\Model\Order\Payment\State\CaptureCommand $subject,
+ callable $proceed,
+ \Magento\Sales\Api\Data\OrderPaymentInterface $payment,
+ $amount,
+ \Magento\Sales\Api\Data\OrderInterface $order
+ ): \Magento\Framework\Phrase {
+ if ($payment->getMethod() === \Afterpay\Afterpay\Gateway\Config\Config::CODE) {
+ $state = Order::STATE_PROCESSING;
+ $status = null;
+ $message = $this->config->getPaymentFlow() == \Afterpay\Afterpay\Model\Config\Source\PaymentFlow::DEFERRED ?
+ 'Authorized and open to capture amount of %1 online.' :
+ 'Captured amount of %1 online.';
+
+ if ($payment->getIsTransactionPending()) {
+ $state = Order::STATE_PAYMENT_REVIEW;
+ $message = 'An amount of %1 will be captured after being approved at the payment gateway.';
+ }
+
+ if ($payment->getIsFraudDetected()) {
+ $state = Order::STATE_PAYMENT_REVIEW;
+ $status = Order::STATUS_FRAUD;
+ $message .= ' Order is suspended as its capturing amount %1 is suspected to be fraudulent.';
+ }
+
+ if (!isset($status)) {
+ $status = $this->statusResolver->getOrderStatusByState($order, $state);
+ }
+
+ $order->setState($state);
+ $order->setStatus($status);
+
+ return __($message, $order->getBaseCurrency()->formatTxt($amount));
+ }
+
+ return $proceed($payment, $amount, $order);
+ }
+}
diff --git a/Setup/Patch/Data/AfterpayPendingExceptionReviewOrderStatus.php b/Setup/Patch/Data/AfterpayPendingExceptionReviewOrderStatus.php
new file mode 100644
index 0000000..7f8246a
--- /dev/null
+++ b/Setup/Patch/Data/AfterpayPendingExceptionReviewOrderStatus.php
@@ -0,0 +1,49 @@
+statusFactory = $statusFactory;
+ $this->statusResource = $statusResource;
+ }
+
+ public function getAliases(): array
+ {
+ return [];
+ }
+
+ public static function getDependencies(): array
+ {
+ return [];
+ }
+
+ public function apply(): self
+ {
+ $status = $this->statusFactory->create();
+ $status->setData([
+ 'status' => PaymentErrorProcessor::ORDER_STATUS_CODE,
+ 'label' => 'Pending Exception Review (Afterpay)',
+ ]);
+
+ try {
+ $this->statusResource->save($status);
+ } catch (AlreadyExistsException $exception) {
+ return $this;
+ }
+
+ $status->assignState('processing', false, false);
+
+ return $this;
+ }
+}
diff --git a/Setup/Patch/Data/UpdateCbtInfoPatch.php b/Setup/Patch/Data/UpdateCbtInfoPatch.php
new file mode 100644
index 0000000..b979315
--- /dev/null
+++ b/Setup/Patch/Data/UpdateCbtInfoPatch.php
@@ -0,0 +1,56 @@
+merchantConfigurationCommand = $merchantConfigurationCommand;
+ $this->storeManager = $storeManager;
+ $this->typeList = $typeList;
+ $this->logger = $logger;
+ }
+
+ public function getAliases(): array
+ {
+ return [];
+ }
+
+ public static function getDependencies(): array
+ {
+ return [];
+ }
+
+ public function apply()
+ {
+ $websites = $this->storeManager->getWebsites(true);
+ foreach ($websites as $website) {
+ $websiteId = (int)$website->getId();
+ try {
+ $this->merchantConfigurationCommand->execute([
+ 'websiteId' => $websiteId
+ ]);
+ $this->typeList->cleanType(\Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER);
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/ViewModel/Container/Container.php b/ViewModel/Container/Container.php
index 681eead..84369de 100644
--- a/ViewModel/Container/Container.php
+++ b/ViewModel/Container/Container.php
@@ -84,9 +84,15 @@ protected function updateContainer(array $jsLayout, bool $remove, string $contai
private function isCurrentCurrencyAvailable(): bool
{
- $currentCurrencyCode = $this->storeManager->getStore()->getCurrentCurrency();
+ $currentCurrencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode();
+ $baseCurrencyCode = $this->storeManager->getStore()->getBaseCurrencyCode();
$allowedCurrencies = $this->config->getAllowedCurrencies();
+ $validCurrencies = array_keys($this->config->getCbtCurrencyLimits());
- return in_array($currentCurrencyCode->getCode(), $allowedCurrencies);
+ if (in_array($baseCurrencyCode, $allowedCurrencies)) {
+ $validCurrencies[] = $baseCurrencyCode;
+ }
+
+ return in_array($currentCurrencyCode, $validCurrencies);
}
}
diff --git a/ViewModel/Container/Cta/Lib.php b/ViewModel/Container/Cta/Lib.php
index 8556b8a..9cdf23d 100644
--- a/ViewModel/Container/Cta/Lib.php
+++ b/ViewModel/Container/Cta/Lib.php
@@ -4,15 +4,43 @@
namespace Afterpay\Afterpay\ViewModel\Container\Cta;
+use Afterpay\Afterpay\Model\Config;
+use Afterpay\Afterpay\Model\Url\Lib\LibUrlProvider;
+use Magento\Store\Model\StoreManagerInterface;
+
class Lib extends \Afterpay\Afterpay\ViewModel\Container\Lib
{
+ private $storeManager;
+
+ public function __construct(
+ StoreManagerInterface $storeManager,
+ Config $config,
+ LibUrlProvider $libUrlProvider,
+ ?string $containerConfigPath = null
+ ) {
+ $this->storeManager = $storeManager;
+ parent::__construct($config, $libUrlProvider, $containerConfigPath);
+ }
+
public function getMinTotalValue(): ?string
{
+ $currencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode();
+ $cbtLimits = $this->config->getCbtCurrencyLimits();
+ if (isset($cbtLimits[$currencyCode])) {
+ return $cbtLimits[$currencyCode]['minimumAmount']['amount'];
+ }
+
return $this->config->getMinOrderTotal();
}
public function getMaxTotalValue(): ?string
{
+ $currencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode();
+ $cbtLimits = $this->config->getCbtCurrencyLimits();
+ if (isset($cbtLimits[$currencyCode])) {
+ return $cbtLimits[$currencyCode]['maximumAmount']['amount'];
+ }
+
return $this->config->getMaxOrderTotal();
}
}
diff --git a/composer.json b/composer.json
index 99666df..2c0376b 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": "4.0.5",
+ "version": "4.1.0",
"require": {
"php": "~7.1.3||~7.2.0||~7.3.0||~7.4.0",
"magento/framework": "^102.0",
diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml
index 6f97991..202f703 100644
--- a/etc/adminhtml/di.xml
+++ b/etc/adminhtml/di.xml
@@ -30,4 +30,7 @@
+
+
+
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index 751690a..55a070a 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -94,7 +94,13 @@
Afterpay\Afterpay\Model\Config\Source\PaymentFlow
payment/afterpay/payment_flow
- here]]>
+ here]]>
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+ payment/afterpay/enable_reversal
+
diff --git a/etc/di.xml b/etc/di.xml
index 6992391..cf54aa7 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -350,6 +350,11 @@
Afterpay\Afterpay\Gateway\Command\GetMerchantConfigurationCommandWrapper
+
+
+ Afterpay\Afterpay\Gateway\Command\GetMerchantConfigurationCommandWrapper
+
+
Afterpay\Afterpay\Gateway\Command\GetMerchantConfigurationCommandWrapper
@@ -369,4 +374,7 @@
+
+
+
diff --git a/etc/module.xml b/etc/module.xml
index 673402a..d5c2cf1 100644
--- a/etc/module.xml
+++ b/etc/module.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/view/frontend/web/js/view/container/express-checkout/product/button.js b/view/frontend/web/js/view/container/express-checkout/product/button.js
index c172bb5..c75a1fa 100644
--- a/view/frontend/web/js/view/container/express-checkout/product/button.js
+++ b/view/frontend/web/js/view/container/express-checkout/product/button.js
@@ -38,10 +38,13 @@ define([
return this._super();
},
_getOnCommenceCheckoutAfterpayMethod: function () {
+ let isBundle = $('#product_addtocart_form').find('#bundleSummary').length;
const parentOnCommenceCheckoutAfterpayMethod = this._super();
- return (actions) => {
- const productSubmitForm = $('#product_addtocart_form');
- productSubmitForm.submit();
+ return (actions) => {
+ if (!isBundle) {
+ const productSubmitForm = $('#product_addtocart_form');
+ productSubmitForm.submit();
+ }
this.onCartUpdated = $.Deferred();
this.onCartUpdated.done(() => parentOnCommenceCheckoutAfterpayMethod(actions))
.fail(() => this._fail(actions, AfterPay.constants.SERVICE_UNAVAILABLE));