├── .gitignore
├── Docs
└── img
│ ├── rest-sequence-diagram.png
│ └── graphql-sequence-diagram.png
├── view
├── frontend
│ ├── web
│ │ ├── template
│ │ │ ├── payment
│ │ │ │ └── info
│ │ │ │ │ ├── checkout-note.html
│ │ │ │ │ └── terms-and-conditions.html
│ │ │ ├── express-checkout
│ │ │ │ ├── button.html
│ │ │ │ └── button-minicart.html
│ │ │ └── cta
│ │ │ │ ├── pdp
│ │ │ │ └── cta.html
│ │ │ │ └── cta.html
│ │ ├── js
│ │ │ ├── view
│ │ │ │ ├── payment
│ │ │ │ │ ├── payments.js
│ │ │ │ │ └── info
│ │ │ │ │ │ └── terms-and-conditions.js
│ │ │ │ └── container
│ │ │ │ │ ├── express-checkout
│ │ │ │ │ └── check-message.js
│ │ │ │ │ ├── cta
│ │ │ │ │ ├── cta.js
│ │ │ │ │ └── pdp
│ │ │ │ │ │ └── cta.js
│ │ │ │ │ └── container.js
│ │ │ ├── service
│ │ │ │ └── container
│ │ │ │ │ ├── cta
│ │ │ │ │ └── modal-options-updater.js
│ │ │ │ │ ├── cart
│ │ │ │ │ ├── mini-cart-data-retriever.js
│ │ │ │ │ ├── cart-data-retriever.js
│ │ │ │ │ └── abstract-data-retriever.js
│ │ │ │ │ ├── configurable-mixin.js
│ │ │ │ │ └── pricebox-widget-mixin.js
│ │ │ ├── action
│ │ │ │ └── create-afterpay-checkout.js
│ │ │ └── model
│ │ │ │ └── container
│ │ │ │ └── container-model-holder.js
│ │ └── css
│ │ │ ├── afterpay-checkout.less
│ │ │ └── afterpay-express-checkout.less
│ ├── layout
│ │ ├── checkout_onepage_success.xml
│ │ ├── catalog_product_view_type_bundle.xml
│ │ └── catalog_product_view_type_grouped.xml
│ ├── templates
│ │ ├── express-checkout
│ │ │ ├── lib.phtml
│ │ │ ├── button.phtml
│ │ │ ├── minicart-headless.phtml
│ │ │ ├── cart-headless.phtml
│ │ │ └── headless.phtml
│ │ └── cta
│ │ │ ├── lib.phtml
│ │ │ ├── container.phtml
│ │ │ ├── minicart
│ │ │ └── headless.phtml
│ │ │ ├── pdp
│ │ │ └── headless.phtml
│ │ │ └── cart
│ │ │ └── headless.phtml
│ └── requirejs-config.js
└── adminhtml
│ ├── web
│ └── js
│ │ └── limits.js
│ └── templates
│ └── system
│ └── config
│ └── button
│ └── update.phtml
├── registration.php
├── Model
├── Log
│ ├── Handler
│ │ └── Debug.php
│ └── Method
│ │ └── Logger.php
├── CBT
│ └── CheckCBTCurrencyAvailabilityInterface.php
├── Spi
│ ├── CheckoutValidatorInterface.php
│ ├── SourceValidatorServiceInterface.php
│ └── StockItemsValidatorInterface.php
├── Checks
│ ├── PaymentMethod.php
│ ├── PaymentMethodInterface.php
│ ├── CanUseForQuoteItems.php
│ └── IsCBTAvailable.php
├── PaymentStateInterface.php
├── Order
│ ├── OrderItemInterface.php
│ ├── Payment
│ │ ├── QuotePaidStorage.php
│ │ └── Auth
│ │ │ ├── TokenValidator.php
│ │ │ ├── TokenSaver.php
│ │ │ └── ExpiryDate.php
│ ├── OrderItem.php
│ └── CreditMemo
│ │ ├── StatusChanger.php
│ │ ├── OrderUpdater.php
│ │ ├── CaptureProcessor.php
│ │ ├── PaymentUpdater.php
│ │ └── CreditMemoInitiator.php
├── Url
│ ├── Lib
│ │ ├── CtaLibUrlProvider.php
│ │ ├── ExpressCheckoutLibUrlProvider.php
│ │ ├── WidgetCheckoutLibUrlProvider.php
│ │ └── LibUrlProvider.php
│ ├── UrlBuilder.php
│ └── UrlBuilder
│ │ └── UrlFactory.php
├── Config
│ ├── CategorySourceRegistry.php
│ └── Source
│ │ ├── ApiMode.php
│ │ └── PaymentFlow.php
├── GraphQl
│ ├── Payment
│ │ └── AfterpayDataProvider.php
│ └── Resolver
│ │ └── AfterpayConfigMiniCart.php
├── Token.php
├── ResourceModel
│ ├── Token
│ │ └── Collection.php
│ ├── NotAllowedProductsProvider.php
│ └── Token.php
├── Payment
│ ├── AdditionalInformationInterface.php
│ └── AmountProcessor
│ │ ├── Shipment.php
│ │ └── VirtualProducts.php
├── RedirectPath.php
├── CheckoutManagement
│ └── ExpressCheckoutValidator.php
├── FeatureFlag
│ └── Service
│ │ └── GetCreditMemoOnGrandTotalEnabled.php
├── Checkout.php
├── Shipment
│ └── Express
│ │ └── ShippingListProvider.php
└── CheckoutConfigProvider.php
├── Api
├── Data
│ ├── TokenInterface.php
│ ├── RedirectPathInterface.php
│ └── CheckoutInterface.php
└── CheckoutManagementInterface.php
├── etc
├── payment.xml
├── adminhtml
│ ├── routes.xml
│ ├── events.xml
│ └── di.xml
├── frontend
│ ├── routes.xml
│ └── sections.xml
├── webapi.xml
├── db_schema_whitelist.json
├── module.xml
├── crontab.xml
├── cron_groups.xml
├── events.xml
├── graphql
│ └── di.xml
└── db_schema.xml
├── Gateway
├── Config
│ └── Config.php
├── Validator
│ ├── ReversalResponseValidator.php
│ ├── CheckoutResponseValidator.php
│ ├── MerchantConfigurationResponseValidator.php
│ ├── RefundResponseValidator.php
│ ├── CaptureResponseValidator.php
│ └── Method
│ │ ├── CurrencyValidator.php
│ │ └── NotAllowedProductsValidator.php
├── Request
│ ├── GetMerchantConfigurationDataBuilder.php
│ ├── Checkout
│ │ └── GetCheckoutDataBuilder.php
│ ├── GetPaymentDataBuilder.php
│ ├── PaymentAction
│ │ ├── ReversalDataBuilder.php
│ │ ├── CaptureDataBuilder.php
│ │ ├── AuthCaptureDataBuilder.php
│ │ └── RefundAndVoidDataBuilder.php
│ └── ExpressCheckoutDataBuilder.php
├── Response
│ ├── Checkout
│ │ ├── CheckoutDataResultHandler.php
│ │ └── CheckoutResultHandler.php
│ ├── MerchantConfiguration
│ │ ├── MpidConfigurationHandler.php
│ │ ├── CreditMemoOnGrandTotalConfigurationHandler.php
│ │ ├── ConsumerLendingConfigurationHandler.php
│ │ ├── LimitConfigurationHandler.php
│ │ ├── CBTAvailableCurrenciesConfigurationHandler.php
│ │ ├── SpecificCountriesConfigurationHandler.php
│ │ └── ChannelsConfigurationHandler.php
│ └── DiscountHandler.php
├── ErrorMessageMapper
│ ├── MerchantConfigurationErrorMessageMapper.php
│ └── CaptureErrorMessageMapper.php
├── Command
│ ├── CommandPoolProxy.php
│ └── RefundAndVoidCommand.php
└── Http
│ └── TransferFactory
│ └── UserAgentProvider.php
├── Cron
├── OfflineCreditMemo.php
└── MerchantConfigurationUpdater.php
├── Test
├── ApiFunctional
│ └── GraphQl
│ │ └── README.md
└── Unit
│ ├── Model
│ ├── Order
│ │ └── Payment
│ │ │ └── QuotePaidStorageTest.php
│ └── Url
│ │ └── UrlBuilderTest.php
│ └── Service
│ └── Payment
│ └── Auth
│ └── ExpiryDateTest.php
├── Plugin
├── Checkout
│ ├── CustomerData
│ │ └── Cart.php
│ └── Block
│ │ └── Cart
│ │ └── Sidebar.php
├── Block
│ ├── Order
│ │ └── Totals.php
│ └── Adminhtml
│ │ ├── Order
│ │ ├── View.php
│ │ └── Creditmemo
│ │ │ └── Create
│ │ │ └── Items.php
│ │ └── CustomerBalance
│ │ └── Order
│ │ └── Creditmemo
│ │ └── Controls.php
├── Catalog
│ └── Model
│ │ └── ResourceModel
│ │ └── Category
│ │ └── Tree.php
├── Payment
│ └── Checks
│ │ └── CanUseForCountryPlugin.php
├── Quote
│ └── CheckoutManagement.php
├── QuoteGraphQl
│ └── Cart
│ │ └── PlaceOrderPlugin.php
└── Sales
│ └── Model
│ └── Service
│ └── CreditmemoService
│ └── AdjustmentAmountValidation.php
├── Block
├── Payment
│ └── Info.php
├── Cta
│ ├── Cart.php
│ ├── Product.php
│ ├── ProductHeadless.php
│ ├── CartHeadless.php
│ └── MiniCartHeadless.php
├── Adminhtml
│ └── System
│ │ └── Config
│ │ ├── Form
│ │ └── Field
│ │ │ ├── Disable.php
│ │ │ ├── Version.php
│ │ │ └── CBTAvailableCurrencies.php
│ │ ├── Button
│ │ └── LimitUpdate.php
│ │ └── Fieldset
│ │ └── AllowedByCountry.php
└── ExpressCheckout
│ ├── Cart.php
│ ├── Product.php
│ ├── ProductHeadless.php
│ ├── CartHeadless.php
│ └── MiniCartHeadless.php
├── composer.json
├── Observer
├── SetQuoteIsPaidByAfterpay.php
├── Payment
│ └── DataAssignObserver.php
├── AuthCaptureBeforeShipment.php
└── AuthCaptureBeforeCompleteOrder.php
├── Setup
└── Patch
│ └── Data
│ ├── AdaptPayments.php
│ ├── AfterpayPendingExceptionReviewOrderStatus.php
│ ├── UpdateCbtInfoPatch.php
│ └── UpdateMPIDInfoPatch.php
├── ViewModel
└── Container
│ ├── Cta
│ ├── Lib.php
│ ├── Cta.php
│ └── Headless.php
│ ├── Lib.php
│ └── ExpressCheckout
│ └── Headless.php
└── Controller
├── Adminhtml
└── MerchantConfiguration
│ └── Update.php
└── Express
└── CreateCheckout.php
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/Docs/img/rest-sequence-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afterpay/afterpay-magento-2/HEAD/Docs/img/rest-sequence-diagram.png
--------------------------------------------------------------------------------
/Docs/img/graphql-sequence-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afterpay/afterpay-magento-2/HEAD/Docs/img/graphql-sequence-diagram.png
--------------------------------------------------------------------------------
/view/frontend/web/template/payment/info/checkout-note.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Model/Log/Handler/Debug.php:
--------------------------------------------------------------------------------
1 | getMethod() == \Afterpay\Afterpay\Gateway\Config\Config::CODE;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/view/frontend/layout/checkout_onepage_success.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Model/Checks/PaymentMethodInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | 0
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/etc/adminhtml/routes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/etc/frontend/routes.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Model/PaymentStateInterface.php:
--------------------------------------------------------------------------------
1 | urlBuilder->build(
10 | \Afterpay\Afterpay\Model\Url\UrlBuilder::TYPE_JS_LIB,
11 | 'square-marketplace.js'
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/view/frontend/web/template/express-checkout/button.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
--------------------------------------------------------------------------------
/Gateway/Config/Config.php:
--------------------------------------------------------------------------------
1 | createResult(true);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Gateway/Request/GetMerchantConfigurationDataBuilder.php:
--------------------------------------------------------------------------------
1 | $buildSubject['websiteId'],
11 | ];
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Model/Url/Lib/ExpressCheckoutLibUrlProvider.php:
--------------------------------------------------------------------------------
1 | urlBuilder->build(
10 | \Afterpay\Afterpay\Model\Url\UrlBuilder::TYPE_JS_LIB,
11 | 'square-marketplace.js'
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/payment/payments.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'uiComponent',
3 | 'Magento_Checkout/js/model/payment/renderer-list'
4 | ], function (Component, rendererList) {
5 | 'use strict';
6 |
7 | rendererList.push(
8 | {
9 | type: 'afterpay',
10 | component: 'Afterpay_Afterpay/js/view/payment/method-renderer/afterpay'
11 | }
12 | );
13 |
14 | return Component.extend({});
15 | });
16 |
--------------------------------------------------------------------------------
/view/frontend/web/js/service/container/cta/modal-options-updater.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery'
3 | ], function ($) {
4 | 'use strict';
5 | return function (id, modalOptions) {
6 | const elem = $('#' + id);
7 | if (elem.length > 0 && elem[0].modalOptions) {
8 | elem[0].modalOptions.locale = modalOptions.locale;
9 | elem[0].modalOptions.cbtEnabled = modalOptions.cbtEnabled;
10 | }
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/etc/webapi.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Model/Config/CategorySourceRegistry.php:
--------------------------------------------------------------------------------
1 | showAllCategories;
12 | }
13 |
14 | public function setShowAllCategories(bool $value): void
15 | {
16 | $this->showAllCategories = $value;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/view/frontend/layout/catalog_product_view_type_bundle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Model/GraphQl/Payment/AfterpayDataProvider.php:
--------------------------------------------------------------------------------
1 | getViewModel();
6 | ?>
7 |
8 | isContainerEnable() && !$viewModel->getIsLibLoadedAlready()): ?>
9 |
10 |
11 |
--------------------------------------------------------------------------------
/view/frontend/web/template/express-checkout/button-minicart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/etc/db_schema_whitelist.json:
--------------------------------------------------------------------------------
1 | {
2 | "afterpay_tokens_log": {
3 | "column": {
4 | "log_id": true,
5 | "order_id": true,
6 | "token": true,
7 | "expiration_date": true
8 | },
9 | "constraint": {
10 | "PRIMARY": true,
11 | "AFTERPAY_TOKENS_LOG_ORDER_ID_SALES_ORDER_ENTITY_ID": true,
12 | "AFTERPAY_TOKENS_LOG_TOKEN": true,
13 | "AFTERPAY_TOKENS_LOG_ORDER_ID": true
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/Model/Token.php:
--------------------------------------------------------------------------------
1 | _init(ResourceModel::class);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/view/frontend/web/js/service/container/cart/mini-cart-data-retriever.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'Afterpay_Afterpay/js/service/container/cart/abstract-data-retriever',
3 | 'Magento_Customer/js/customer-data',
4 | ], function (AbstractDataRetriever, customerData) {
5 | return AbstractDataRetriever.extend({
6 | defaults: {
7 | isVisible: false,
8 | dataAmount: "0",
9 | modelWithPrice: customerData.get('cart'),
10 | priceKey: "subtotalAmount"
11 | },
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/Gateway/Response/Checkout/CheckoutDataResultHandler.php:
--------------------------------------------------------------------------------
1 | getPayment();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Cron/OfflineCreditMemo.php:
--------------------------------------------------------------------------------
1 | statusChanger = $statusChanger;
13 | }
14 |
15 | public function execute(): void
16 | {
17 | $this->statusChanger->execute();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Model/Spi/SourceValidatorServiceInterface.php:
--------------------------------------------------------------------------------
1 |
8 |
9 | ```
10 |
11 | ## Run
12 | ```
13 | vendor/bin/phpunit -c dev/tests/api-functional/phpunit_graphql.xml app/code/Afterpay/Afterpay/Test/ApiFunctional/
14 | ```
15 |
16 | Please also refer to the documentation:
17 |
18 | https://devdocs.magento.com/guides/v2.4/graphql/functional-testing.html
19 |
--------------------------------------------------------------------------------
/etc/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Plugin/Checkout/CustomerData/Cart.php:
--------------------------------------------------------------------------------
1 | getProduct()->getIsVirtual();
18 | return $result;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/view/frontend/web/js/service/container/cart/cart-data-retriever.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'Afterpay_Afterpay/js/service/container/cart/abstract-data-retriever',
3 | 'Magento_Checkout/js/model/totals'
4 | ], function (AbstractDataRetriever, totalsModel) {
5 | return AbstractDataRetriever.extend({
6 | defaults: {
7 | isVisible: false,
8 | dataAmount: "0",
9 | modelWithPrice: totalsModel.totals,
10 | priceKey: (checkoutConfig.payment.afterpay.isCBTCurrency === true ) ? "grand_total" : "base_grand_total"
11 | },
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/Model/ResourceModel/Token/Collection.php:
--------------------------------------------------------------------------------
1 | _init(Model::class, ResourceModel::class);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Model/Spi/StockItemsValidatorInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/etc/crontab.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 0 6 * * *
6 |
7 |
8 | */5 * * * *
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Model/Payment/AdditionalInformationInterface.php:
--------------------------------------------------------------------------------
1 | quotesOrderPayments[$quoteId] = $afterpayPayment;
14 | return $this;
15 | }
16 |
17 | public function getAfterpayPaymentIfQuoteIsPaid(int $quoteId): ?Payment
18 | {
19 | return $this->quotesOrderPayments[$quoteId] ?? null;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/view/frontend/templates/cta/lib.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
6 | ?>
7 |
8 | isContainerEnable() && !$viewModel->getIsLibLoadedAlready()): ?>
9 |
14 |
15 |
--------------------------------------------------------------------------------
/view/frontend/templates/cta/container.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
5 | ?>
6 |
7 | isContainerEnable()): ?>
8 |
9 |
10 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/Block/Payment/Info.php:
--------------------------------------------------------------------------------
1 | setData('is_pdf', true);
10 | return parent::toPdf();
11 | }
12 |
13 | public function getSpecificInformation(): array
14 | {
15 | if ($this->getData('is_pdf')) {
16 | return [];
17 | }
18 | return parent::getSpecificInformation();
19 | }
20 |
21 | protected function getLabel($field)
22 | {
23 | return ucfirst(str_replace('_', ' ', $field));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Model/Config/Source/ApiMode.php:
--------------------------------------------------------------------------------
1 | self::SANDBOX,
15 | 'label' => __('Sandbox')
16 | ],
17 | [
18 | 'value' => self::PRODUCTION,
19 | 'label' => __('Production')
20 | ]
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/etc/cron_groups.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1
5 | 4
6 | 2
7 | 10
8 | 60
9 | 600
10 | 1
11 |
12 |
13 |
--------------------------------------------------------------------------------
/view/frontend/web/template/cta/pdp/cta.html:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
--------------------------------------------------------------------------------
/Gateway/Validator/CheckoutResponseValidator.php:
--------------------------------------------------------------------------------
1 | createResult(false, [$response['message']], [$response['errorCode']]);
13 | }
14 |
15 | return $this->createResult(true);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Gateway/Validator/MerchantConfigurationResponseValidator.php:
--------------------------------------------------------------------------------
1 | createResult(false, [], [$response['errorCode']]);
13 | }
14 |
15 | return $this->createResult(true);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Model/Config/Source/PaymentFlow.php:
--------------------------------------------------------------------------------
1 | self::IMMEDIATE,
15 | 'label' => __('Immediate Payment Flow')
16 | ],
17 | [
18 | 'value' => self::DEFERRED,
19 | 'label' => __('Deferred Payment Flow')
20 | ]
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/view/frontend/web/template/cta/cta.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
--------------------------------------------------------------------------------
/Gateway/ErrorMessageMapper/MerchantConfigurationErrorMessageMapper.php:
--------------------------------------------------------------------------------
1 | getOrder();
10 |
11 | if (!$order) {
12 | return $result;
13 | }
14 |
15 | $isCbtCurrency = (bool) $order->getPayment()->getAdditionalInformation(
16 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY
17 | );
18 |
19 | if ($isCbtCurrency === true) {
20 | unset($result['base_grandtotal']);
21 | }
22 |
23 | return $result;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/view/frontend/templates/express-checkout/button.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
5 | ?>
6 | isContainerEnable()): ?>
7 |
8 |
9 |
10 |
17 |
18 |
--------------------------------------------------------------------------------
/Api/Data/RedirectPathInterface.php:
--------------------------------------------------------------------------------
1 | getValidatorPool()->get('quote_items');
14 | } catch (\Throwable $e) {
15 | return true;
16 | }
17 |
18 | $result = $quoteItemsValidator->validate(['quote' => $quote]);
19 | return $result->isValid();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Model/Order/Payment/Auth/TokenValidator.php:
--------------------------------------------------------------------------------
1 | tokensResource = $tokensResource;
15 | }
16 |
17 | public function checkIsUsed(string $token): bool
18 | {
19 | if (!isset($this->results[$token])) {
20 | $this->results[$token] = (bool)$this->tokensResource->selectByToken($token);
21 | }
22 |
23 | return $this->results[$token];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/view/frontend/templates/cta/minicart/headless.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
5 | $jsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/cta/minicart/headless.js');
6 | ?>
7 |
15 |
16 |
--------------------------------------------------------------------------------
/view/frontend/templates/cta/pdp/headless.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
5 | $jsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/cta/pdp/headless.js');
6 | ?>
7 |
13 |
14 |
--------------------------------------------------------------------------------
/view/frontend/layout/catalog_product_view_type_grouped.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Gateway/Response/MerchantConfiguration/MpidConfigurationHandler.php:
--------------------------------------------------------------------------------
1 | config = $config;
13 | }
14 |
15 | public function handle(array $handlingSubject, array $response): void
16 | {
17 | $websiteId = (int)$handlingSubject['websiteId'];
18 | $mpid = $response['publicId'] ?? '';
19 | $this->config->setPublicId($mpid, $websiteId);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Model/Url/Lib/WidgetCheckoutLibUrlProvider.php:
--------------------------------------------------------------------------------
1 | config = $config;
15 | }
16 |
17 | protected function buildUrl(): string
18 | {
19 | return $this->urlBuilder->build(
20 | \Afterpay\Afterpay\Model\Url\UrlBuilder::TYPE_JS_LIB,
21 | 'square-marketplace.js'
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Gateway/Request/Checkout/GetCheckoutDataBuilder.php:
--------------------------------------------------------------------------------
1 | getPayment()->getAdditionalInformation(
14 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_TOKEN
15 | );
16 | return [
17 | 'storeId' => $payment->getOrder()->getStoreId(),
18 | 'afterpayToken' => $afterpayToken
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Gateway/Request/GetPaymentDataBuilder.php:
--------------------------------------------------------------------------------
1 | getPayment()->getAdditionalInformation(
14 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_ORDER_ID
15 | );
16 | return [
17 | 'storeId' => $payment->getOrder()->getStoreId(),
18 | 'orderId' => $afterpayOrderId,
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Gateway/Request/PaymentAction/ReversalDataBuilder.php:
--------------------------------------------------------------------------------
1 | getPayment()->getAdditionalInformation(
14 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_TOKEN
15 | );
16 |
17 | return [
18 | 'storeId' => $payment->getOrder()->getStoreId(),
19 | 'afterpayToken' => $afterpayToken
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Block/Cta/Cart.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | if ($this->config->getIsEnableCtaCartPage() && !$this->config->getIsEnableCartPageHeadless()) {
24 | return parent::_toHtml();
25 | }
26 |
27 | return '';
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Model/RedirectPath.php:
--------------------------------------------------------------------------------
1 | confirmPath = $path;
13 | return $this;
14 | }
15 |
16 | public function getConfirmPath(): string
17 | {
18 | return $this->confirmPath;
19 | }
20 |
21 | public function setCancelPath(string $path): self
22 | {
23 | $this->cancelPath = $path;
24 | return $this;
25 | }
26 |
27 | public function getCancelPath(): string
28 | {
29 | return $this->cancelPath;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Block/Cta/Product.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | if ($this->config->getIsEnableCtaProductPage() && !$this->config->getIsEnableProductPageHeadless()) {
24 | return parent::_toHtml();
25 | }
26 |
27 | return '';
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Model/Order/Payment/Auth/TokenSaver.php:
--------------------------------------------------------------------------------
1 | tokensResource = $tokensResource;
18 | $this->dateTime = $dateTime;
19 | }
20 |
21 | public function execute(int $orderId, string $token, ?string $expiryDate): bool
22 | {
23 | return (bool)$this->tokensResource->insertNewToken($orderId, $token, $this->dateTime->formatDate($expiryDate));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/Form/Field/Disable.php:
--------------------------------------------------------------------------------
1 | unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
13 | return parent::render($element);
14 | }
15 |
16 | protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
17 | {
18 | /** @phpstan-ignore-next-line */
19 | $element->setDisabled('disabled');
20 | return $element->getElementHtml();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/view/frontend/web/js/service/container/configurable-mixin.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | "Afterpay_Afterpay/js/model/container/container-model-holder"
4 | ], function ($, containerModelHolder) {
5 | 'use strict';
6 | const containerModel = containerModelHolder.getModel("afterpay-pdp-container");
7 | const configurableMixin = {
8 | _configureElement: function (element) {
9 | const res = this._super(element);
10 | if (this.simpleProduct) {
11 | containerModel.setCurrentProductId(this.simpleProduct);
12 | }
13 | return res;
14 | }
15 | };
16 | return function (targetWidget) {
17 | $.widget('mage.configurable', targetWidget, configurableMixin);
18 |
19 | return $.mage.configurable;
20 | };
21 | });
22 |
--------------------------------------------------------------------------------
/Model/Order/OrderItem.php:
--------------------------------------------------------------------------------
1 | refundedQty;
13 | }
14 |
15 | public function setAfterpayRefundedQty(float $qty): OrderItemInterface
16 | {
17 | $this->refundedQty = $qty;
18 | return $this;
19 | }
20 |
21 | public function getAfterpayVoidedQty(): float
22 | {
23 | return $this->voidedQty;
24 | }
25 |
26 | public function setAfterpayVoidedQty(float $qty): OrderItemInterface
27 | {
28 | $this->voidedQty = $qty;
29 | return $this;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/view/adminhtml/web/js/limits.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery'
3 | ], function ($) {
4 | return function (options, selector) {
5 | $(options.spinnerId).hide();
6 | $(selector).click(function (e) {
7 | e.preventDefault();
8 | $(selector).addClass('disabled');
9 | $(options.spinnerId).show();
10 | $.post(
11 | options.actionUrl,
12 | {
13 | websiteId: $('#website_switcher').val()
14 | }
15 | ).done(() => location.reload())
16 | .always(function () {
17 | $(selector).removeClass('disabled');
18 | $(options.spinnerId).hide();
19 | })
20 | e.stopPropagation();
21 | return false;
22 | });
23 | }
24 | });
25 |
--------------------------------------------------------------------------------
/Block/ExpressCheckout/Cart.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | if ($this->config->getIsEnableExpressCheckoutCartPage() && !$this->config->getIsEnableCartPageHeadless()) {
24 | return parent::_toHtml();
25 | }
26 |
27 | return '';
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Block/ExpressCheckout/Product.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | if ($this->config->getIsEnableExpressCheckoutProductPage() && !$this->config->getIsEnableProductPageHeadless()) {
24 | return parent::_toHtml();
25 | }
26 |
27 | return '';
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Gateway/Validator/RefundResponseValidator.php:
--------------------------------------------------------------------------------
1 | createResult(false, [$response['message']], [$response['errorCode']]);
13 | }
14 |
15 | if (isset($response['refundId'])) {
16 | return $this->createResult(true);
17 | }
18 |
19 | return $this->createResult(false, [__('Unknown result has been returned')]);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Model/Payment/AmountProcessor/Shipment.php:
--------------------------------------------------------------------------------
1 | getOrderItem()->getParentItem()) {
15 | $amount += $this->calculateItemPrice($payment, $item->getOrderItem(), (float)$item->getQty());
16 | }
17 | }
18 | $amount += $this->getShippingAmount($payment->getOrder());
19 |
20 | return $this->processDiscount($amount, $payment);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Model/Url/Lib/LibUrlProvider.php:
--------------------------------------------------------------------------------
1 | urlBuilder = $urlBuilder;
14 | }
15 |
16 | public function getAfterpayLib(): ?string
17 | {
18 | if (!$this->isLibGotten) {
19 | $this->isLibGotten = true;
20 | return $this->buildUrl();
21 | }
22 | return null;
23 | }
24 |
25 | public function getIsLibGotten(): bool
26 | {
27 | return $this->isLibGotten;
28 | }
29 |
30 | abstract protected function buildUrl(): string;
31 | }
32 |
--------------------------------------------------------------------------------
/Gateway/ErrorMessageMapper/CaptureErrorMessageMapper.php:
--------------------------------------------------------------------------------
1 | config = $config;
16 | }
17 |
18 | public function handle(array $handlingSubject, array $response): void
19 | {
20 | $websiteId = (int)$handlingSubject['websiteId'];
21 | $mpid = $response['publicId'] ?? '';
22 | $flagValue = false; // TODO: replace it with a flag pull
23 | $this->config->setIsCreditMemoGrandTotalOnlyEnabled((int)$flagValue, $websiteId);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Model/Order/Payment/Auth/ExpiryDate.php:
--------------------------------------------------------------------------------
1 | timezone = $timezone;
15 | }
16 |
17 | public function format(?string $date = null): string
18 | {
19 | return $this->timezone->date($date)->format(static::FORMAT);
20 | }
21 |
22 | public function isExpired(string $expireDate, ?string $dateToCheck = null): bool
23 | {
24 | if ($dateToCheck == null) {
25 | $dateToCheck = $this->format();
26 | }
27 | return strtotime($expireDate) < strtotime($dateToCheck);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/etc/adminhtml/events.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
9 |
11 |
12 |
13 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "afterpay-global/module-afterpay",
3 | "license": "Apache-2.0",
4 | "type": "magento2-module",
5 | "description": "Magento 2 Afterpay Payment Module",
6 | "version": "5.4.4",
7 | "require": {
8 | "php": ">=7.4.0",
9 | "magento/framework": "^103.0",
10 | "magento/module-checkout": "100.4.*",
11 | "magento/module-payment": "100.4.*",
12 | "magento/module-quote-graph-ql": "100.4.*",
13 | "magento/module-sales": "103.0.*",
14 | "magento/module-weee": "100.4.*",
15 | "afterpay-global/module-cash-app-pay" : "^5"
16 | },
17 | "authors": [
18 | {
19 | "name": "Afterpay",
20 | "homepage": "https://www.afterpay.com"
21 | }
22 | ],
23 | "autoload": {
24 | "files": [
25 | "registration.php"
26 | ],
27 | "psr-4": {
28 | "Afterpay\\Afterpay\\": ""
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/view/frontend/requirejs-config.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | map: {
3 | '*': {
4 | "afterpayBaseContainer": "Afterpay_Afterpay/js/view/container/container",
5 | "afterpayCta": "Afterpay_Afterpay/js/view/container/cta/cta",
6 | "afterpayCtaPdp": "Afterpay_Afterpay/js/view/container/cta/pdp/cta",
7 | "afterpayExpressCheckoutButton": "Afterpay_Afterpay/js/view/container/express-checkout/button",
8 | "afterpayExpressCheckoutButtonPdp": "Afterpay_Afterpay/js/view/container/express-checkout/product/button"
9 | }
10 | },
11 | config: {
12 | mixins: {
13 | "Magento_Catalog/js/price-box": {
14 | "Afterpay_Afterpay/js/service/container/pricebox-widget-mixin": true
15 | },
16 | "Magento_ConfigurableProduct/js/configurable": {
17 | "Afterpay_Afterpay/js/service/container/configurable-mixin": true
18 | }
19 | }
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/Model/CheckoutManagement/ExpressCheckoutValidator.php:
--------------------------------------------------------------------------------
1 | config = $config;
13 | }
14 |
15 | /**
16 | * @inheritDoc
17 | */
18 | public function validate(\Magento\Quote\Model\Quote $quote): void
19 | {
20 | $grandTotal = $quote->getBaseGrandTotal();
21 | if ($grandTotal < $this->config->getMinOrderTotal() ||
22 | $grandTotal > $this->config->getMaxOrderTotal()) {
23 | throw new \Magento\Framework\Validation\ValidationException(
24 | __('Order amount exceed Afterpay order limit.')
25 | );
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/view/adminhtml/templates/system/config/button/update.phtml:
--------------------------------------------------------------------------------
1 |
5 |
15 |
25 |
--------------------------------------------------------------------------------
/Block/Cta/ProductHeadless.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | /** @var \Afterpay\Afterpay\ViewModel\Container\Cta\Headless $viewModel */
24 | $viewModel = $this->getViewModel();
25 | if ($viewModel && $viewModel->isContainerEnable()
26 | && $this->config->getIsEnableCtaProductPage() && $this->config->getIsEnableProductPageHeadless()) {
27 | return parent::_toHtml();
28 | }
29 |
30 | return '';
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/view/frontend/templates/cta/cart/headless.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
5 | $jsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/cta/cart/headless.js');
6 | ?>
7 |
21 |
22 |
--------------------------------------------------------------------------------
/Model/Url/UrlBuilder.php:
--------------------------------------------------------------------------------
1 | urlFactory = $urlFactory;
16 | }
17 |
18 | public function build(string $type, string $path, array $pathArgs = [], ?int $storeId = null): string
19 | {
20 | return $this->urlFactory->create($type, $storeId, $pathArgs) . $this->replaceArgsInPath($path, $pathArgs);
21 | }
22 |
23 | private function replaceArgsInPath(string $path, array $args): string
24 | {
25 | foreach ($args as $argKey => $argVal) {
26 | $path = str_replace('{' . $argKey . '}', (string)$argVal, $path);
27 | }
28 | return $path;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Plugin/Catalog/Model/ResourceModel/Category/Tree.php:
--------------------------------------------------------------------------------
1 | categorySourceRegistry = $categorySourceRegistry;
12 | }
13 |
14 | public function beforeAddCollectionData(
15 | \Magento\Catalog\Model\ResourceModel\Category\Tree $subject,
16 | $collection = null,
17 | $sorted = false,
18 | $exclude = [],
19 | $toLoad = true,
20 | $onlyActive = false
21 | ): array {
22 | return [
23 | $collection,
24 | $sorted,
25 | $exclude,
26 | $toLoad,
27 | $this->categorySourceRegistry->getShowAllCategories() ? false : $onlyActive
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Block/ExpressCheckout/ProductHeadless.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | /** @var \Afterpay\Afterpay\ViewModel\Container\ExpressCheckout\Headless $viewModel */
24 | $viewModel = $this->getViewModel();
25 | if ($viewModel && $viewModel->isContainerEnable()
26 | && $this->config->getIsEnableExpressCheckoutProductPage() && $this->config->getIsEnableProductPageHeadless()) {
27 | return parent::_toHtml();
28 | }
29 |
30 | return '';
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Gateway/Response/MerchantConfiguration/ConsumerLendingConfigurationHandler.php:
--------------------------------------------------------------------------------
1 | config = $config;
13 | }
14 |
15 | public function handle(array $handlingSubject, array $response): void
16 | {
17 | $websiteId = (int)$handlingSubject['websiteId'];
18 | $consumerLending = $response['consumerLending']['enabled'] ?? false;
19 | $this->config->setConsumerLendingEnabled((int)$consumerLending, $websiteId);
20 | if ($consumerLending) {
21 | $minAmount = $response['consumerLending']['minimumAmount']['amount'] ?? null;
22 | $this->config->setConsumerLendingMinAmount($minAmount, $websiteId);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Plugin/Payment/Checks/CanUseForCountryPlugin.php:
--------------------------------------------------------------------------------
1 | getCode() !== \Afterpay\Afterpay\Gateway\Config\Config::CODE) {
14 | return $result;
15 | }
16 |
17 | $billingAddress = $quote->getBillingAddress();
18 | if ($billingAddress->getCountry()) {
19 | return $paymentMethod->canUseForCountry($billingAddress->getCountry());
20 | }
21 |
22 | $shippingAddress = $quote->getShippingAddress();
23 | if ($shippingAddress->getCountry()) {
24 | return $paymentMethod->canUseForCountry($shippingAddress->getCountry());
25 | }
26 |
27 | return $result;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Block/Cta/CartHeadless.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | /** @var \Afterpay\Afterpay\ViewModel\Container\Cta\Headless $viewModel */
24 | $viewModel = $this->getViewModel();
25 | if ($viewModel && $viewModel->isContainerEnable() && $this->isEnabledForCart()) {
26 | return parent::_toHtml();
27 | }
28 |
29 | return '';
30 | }
31 | public function isEnabledForCart(): bool
32 | {
33 | return $this->config->getIsEnableCtaCartPage() && $this->config->getIsEnableCartPageHeadless();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/Form/Field/Version.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
24 | }
25 |
26 | /**
27 | * @inheritDoc
28 | */
29 | protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
30 | {
31 | return $this->resource->getDataVersion(self::MODULE_NAME) ?: "";
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Block/Cta/MiniCartHeadless.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | /** @var \Afterpay\Afterpay\ViewModel\Container\Cta\Headless $viewModel */
24 | $viewModel = $this->getViewModel();
25 | if ($viewModel && $viewModel->isContainerEnable() && $this->isEnabledForMinicart()) {
26 | return parent::_toHtml();
27 | }
28 |
29 | return '';
30 | }
31 |
32 | public function isEnabledForMinicart(): bool
33 | {
34 | return $this->config->getIsEnableCtaMiniCart() && $this->config->getIsEnableMiniCartHeadless();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Model/Order/CreditMemo/StatusChanger.php:
--------------------------------------------------------------------------------
1 | ordersRetriever = $ordersRetriever;
17 | $this->creditMemoProcessor = $creditMemoProcessor;
18 | $this->logger = $logger;
19 | }
20 |
21 | public function execute(): void
22 | {
23 | $orders = $this->ordersRetriever->getAfterpayOrders();
24 | foreach ($orders as $order) {
25 | try {
26 | $this->creditMemoProcessor->processOrder($order);
27 | } catch (\Magento\Framework\Exception\LocalizedException $e) {
28 | $this->logger->error($e->getMessage());
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/view/frontend/web/js/action/create-afterpay-checkout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @api
3 | */
4 | define([
5 | 'Magento_Checkout/js/model/quote',
6 | 'Magento_Checkout/js/model/url-builder',
7 | 'mage/storage',
8 | 'Magento_Checkout/js/model/error-processor',
9 | 'Magento_Checkout/js/model/full-screen-loader',
10 | ], function (quote, urlBuilder, storage, errorProcessor, fullScreenLoader) {
11 | 'use strict';
12 |
13 | return function (messageContainer, redirectPath) {
14 | const payload = {
15 | cartId: quote.getQuoteId(),
16 | redirectPath: redirectPath
17 | };
18 |
19 | const serviceUrl = urlBuilder.createUrl('/afterpay/checkout', {});
20 |
21 | fullScreenLoader.startLoader();
22 |
23 | return storage.post(
24 | serviceUrl,
25 | JSON.stringify(payload),
26 | true,
27 | 'application/json'
28 | ).fail(function (response) {
29 | errorProcessor.process(response, messageContainer);
30 | }).always(function () {
31 | fullScreenLoader.stopLoader();
32 | });
33 | };
34 | });
35 |
--------------------------------------------------------------------------------
/Block/ExpressCheckout/CartHeadless.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | /** @var \Afterpay\Afterpay\ViewModel\Container\ExpressCheckout\Headless $viewModel */
24 | $viewModel = $this->getViewModel();
25 | if ($viewModel && $viewModel->isContainerEnable() && $this->isEnabledForCart()) {
26 | return parent::_toHtml();
27 | }
28 |
29 | return '';
30 | }
31 | public function isEnabledForCart(): bool
32 | {
33 | return $this->config->getIsEnableExpressCheckoutCartPage() && $this->config->getIsEnableCartPageHeadless();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Block/ExpressCheckout/MiniCartHeadless.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | }
20 |
21 | protected function _toHtml()
22 | {
23 | /** @var \Afterpay\Afterpay\ViewModel\Container\ExpressCheckout\Headless $viewModel */
24 | $viewModel = $this->getViewModel();
25 | if ($viewModel && $viewModel->isContainerEnable() && $this->isEnabledForMinicart()) {
26 | return parent::_toHtml();
27 | }
28 |
29 | return '';
30 | }
31 |
32 | public function isEnabledForMinicart(): bool
33 | {
34 | return $this->config->getIsEnableExpressCheckoutMiniCart() && $this->config->getIsEnableMiniCartHeadless();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Observer/SetQuoteIsPaidByAfterpay.php:
--------------------------------------------------------------------------------
1 | quotePaidStorage = $quotePaidStorage;
15 | $this->checkPaymentMethod = $checkPaymentMethod;
16 | }
17 |
18 | public function execute(\Magento\Framework\Event\Observer $observer)
19 | {
20 | /** @var \Magento\Sales\Model\Order\Payment $payment */
21 | $payment = $observer->getEvent()->getData('payment');
22 |
23 | if ($this->checkPaymentMethod->isAfterPayMethod($payment)) {
24 | $this->quotePaidStorage->setAfterpayPaymentForQuote((int)$payment->getOrder()->getQuoteId(), $payment);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Gateway/Response/Checkout/CheckoutResultHandler.php:
--------------------------------------------------------------------------------
1 | getPayment($handlingSubject);
10 |
11 | $payment->setAdditionalInformation(
12 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_TOKEN,
13 | $response['token']
14 | );
15 | $payment->setAdditionalInformation(
16 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_AUTH_TOKEN_EXPIRES,
17 | $response['expires']
18 | );
19 | $payment->setAdditionalInformation(
20 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_REDIRECT_CHECKOUT_URL,
21 | $response['redirectCheckoutUrl']
22 | );
23 | }
24 |
25 | protected function getPayment(array $handlingSubject): \Magento\Payment\Model\InfoInterface
26 | {
27 | $quote = $handlingSubject['quote'];
28 | return $quote->getPayment();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Gateway/Validator/CaptureResponseValidator.php:
--------------------------------------------------------------------------------
1 | createResult(
16 | false,
17 | [self::STATUS_DECLINED]
18 | );
19 | }
20 |
21 | if (isset($response['status']) && $response['status'] == self::STATUS_APPROVED) {
22 | return $this->createResult(true);
23 | }
24 |
25 | if (isset($response['errorCode'])) {
26 | return $this->createResult(false, [$response['message']], [$response['errorCode']]);
27 | }
28 |
29 | return $this->createResult(false, ['Unknown status has been returned']);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Plugin/Block/Adminhtml/Order/View.php:
--------------------------------------------------------------------------------
1 | checkPaymentMethod = $checkPaymentMethod;
12 | }
13 |
14 | /**
15 | * @param string $buttonId
16 | * @param \Magento\Sales\Block\Adminhtml\Order\View $result
17 | * @return \Magento\Sales\Block\Adminhtml\Order\View
18 | */
19 | public function afterAddButton(
20 | \Magento\Sales\Block\Adminhtml\Order\View $orderView,
21 | $result,
22 | $buttonId
23 | ) {
24 | if ($buttonId !== 'order_creditmemo') {
25 | return $result;
26 | }
27 | $order = $orderView->getOrder();
28 | $payment = $order->getPayment();
29 | if ($payment == null) {
30 | return $result;
31 | }
32 | if ($this->checkPaymentMethod->isAfterPayMethod($payment)) {
33 | $orderView->removeButton($buttonId);
34 | }
35 | return $result;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/etc/events.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Gateway/Response/MerchantConfiguration/LimitConfigurationHandler.php:
--------------------------------------------------------------------------------
1 | config = $config;
13 | }
14 |
15 | public function handle(array $handlingSubject, array $response): void
16 | {
17 | $websiteId = (int)$handlingSubject['websiteId'];
18 | if (isset($response['maximumAmount']['amount'])) {
19 | $minimumAmount = $response['minimumAmount']['amount'] ?? "0";
20 | $minimumAmount = (string)max((float)$minimumAmount, 1);
21 | $maximumAmount = $response['maximumAmount']['amount'];
22 | $this->config
23 | ->setMinOrderTotal($minimumAmount, $websiteId)
24 | ->setMaxOrderTotal($maximumAmount, $websiteId);
25 | } else {
26 | $this->config
27 | ->deleteMaxOrderTotal($websiteId)
28 | ->deleteMinOrderTotal($websiteId);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Model/FeatureFlag/Service/GetCreditMemoOnGrandTotalEnabled.php:
--------------------------------------------------------------------------------
1 | client = $client;
17 | }
18 |
19 | /**
20 | * Pulls the "credit-memo-on-grand-total-enabled" feature flag value from the internal API.
21 | *
22 | * @param string $mpid Merchant Public ID - Afterpay\Afterpay\Model\Config::getPublicId
23 | * @param string $countryCode Merchant Country ID - Afterpay\Afterpay\Model\Config::getMerchantCountry
24 | *
25 | * @return bool
26 | * @throws LocalizedException
27 | */
28 | public function execute(string $mpid, string $countryCode): bool
29 | {
30 | $response = $this->client->execute(self::CREDIT_MEMP_ON_GRAND_TOTAL_ENABLED_FLAG, $mpid, $countryCode);
31 |
32 | return $response['value'] ?? false;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Model/Checkout.php:
--------------------------------------------------------------------------------
1 | afterpayToken = $token;
14 | return $this;
15 | }
16 |
17 | public function getAfterpayToken(): string
18 | {
19 | return $this->afterpayToken;
20 | }
21 |
22 | public function setAfterpayAuthTokenExpires(string $authTokenExpires): self
23 | {
24 | $this->afterpayAuthTokenExpires = $authTokenExpires;
25 | return $this;
26 | }
27 |
28 | public function getAfterpayAuthTokenExpires(): string
29 | {
30 | return $this->afterpayAuthTokenExpires;
31 | }
32 |
33 | public function setAfterpayRedirectCheckoutUrl(string $redirectCheckoutUrl): self
34 | {
35 | $this->afterpayRedirectCheckoutUrl = $redirectCheckoutUrl;
36 | return $this;
37 | }
38 |
39 | public function getAfterpayRedirectCheckoutUrl(): string
40 | {
41 | return $this->afterpayRedirectCheckoutUrl;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Model/ResourceModel/NotAllowedProductsProvider.php:
--------------------------------------------------------------------------------
1 | config = $config;
15 | $this->resourceConnection = $resourceConnection;
16 | }
17 |
18 | public function provideIds(?int $storeId = null): array
19 | {
20 | $excludedCategoriesIds = $this->config->getExcludeCategories($storeId);
21 | if (empty($excludedCategoriesIds)) {
22 | return [];
23 | }
24 |
25 | $connection = $this->resourceConnection->getConnection();
26 | $select = $connection->select()->from(
27 | ['cat' => $this->resourceConnection->getTableName('catalog_category_product')],
28 | 'cat.product_id'
29 | )->where($connection->prepareSqlCondition('cat.category_id', ['in' => $excludedCategoriesIds]));
30 |
31 | return $connection->fetchCol($select);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Test/Unit/Model/Order/Payment/QuotePaidStorageTest.php:
--------------------------------------------------------------------------------
1 | createMock(\Magento\Sales\Model\Order\Payment::class);
12 |
13 | $testAfterpayOrderPaymentMock->expects($this->any())
14 | ->method('getId')
15 | ->willReturn($testPaymentId);
16 |
17 | $quotePaidStorage = new \Afterpay\Afterpay\Model\Order\Payment\QuotePaidStorage();
18 | $quotePaidStorage->setAfterpayPaymentForQuote($testQuoteId, $testAfterpayOrderPaymentMock);
19 | static::assertSame(
20 | $testAfterpayOrderPaymentMock->getId(),
21 | $quotePaidStorage->getAfterpayPaymentIfQuoteIsPaid($testQuoteId)->getId()
22 | );
23 | }
24 |
25 | public function testEmptyPayment()
26 | {
27 | $testUnexistedQuoteId = 99;
28 |
29 | $quotePaidStorage = new \Afterpay\Afterpay\Model\Order\Payment\QuotePaidStorage();
30 | static::assertSame($quotePaidStorage->getAfterpayPaymentIfQuoteIsPaid($testUnexistedQuoteId), null);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/etc/graphql/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | - Afterpay\Afterpay\Model\GraphQl\Payment\AfterpayDataProvider
8 |
9 |
10 |
11 |
12 |
14 |
15 |
16 |
18 |
19 |
20 |
21 | Afterpay\Afterpay\Gateway\Command\ValidateCheckoutDataCommand
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/container/express-checkout/check-message.js:
--------------------------------------------------------------------------------
1 | window.addEventListener("load", (event) => {
2 |
3 | let url = window.location.href;
4 |
5 | if (url.includes('success')) {
6 | const handleMessages = () => {
7 | let messages = document.querySelectorAll(".message.success");
8 |
9 | messages?.forEach((message) => {
10 | let links = message.getElementsByTagName('a');
11 |
12 | if(links.length > 0) {
13 | for (let link of links) {
14 | if (link.href.includes("checkout/cart")) {
15 | message.remove();
16 | break;
17 | }
18 | }
19 | }else {
20 | message.classList.add("show-message");
21 | }
22 | });
23 | };
24 |
25 | handleMessages();
26 |
27 | const observer = new MutationObserver((mutations) => {
28 | mutations.forEach((mutation) => {
29 | if (mutation.addedNodes.length > 0) {
30 | handleMessages();
31 | }
32 | });
33 | });
34 |
35 | observer.observe(document.body, { childList: true, subtree: true });
36 | }
37 | });
38 |
--------------------------------------------------------------------------------
/Gateway/Response/MerchantConfiguration/CBTAvailableCurrenciesConfigurationHandler.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | $this->serializer = $serializer;
20 | }
21 |
22 | public function handle(array $handlingSubject, array $response): void
23 | {
24 | $websiteId = (int)$handlingSubject['websiteId'];
25 | $cbtAvailableCurrencies = '';
26 |
27 | if (isset($response['CBT']['enabled']) &&
28 | isset($response['CBT']['limits']) &&
29 | is_array($response['CBT']['limits'])
30 | ) {
31 | $cbtAvailableCurrencies = $this->serializer->serialize($response['CBT']['limits']);
32 | }
33 |
34 | $this->config->setCbtCurrencyLimits($cbtAvailableCurrencies, $websiteId);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Model/Order/CreditMemo/OrderUpdater.php:
--------------------------------------------------------------------------------
1 | orderRepository = $orderRepository;
16 | }
17 | public function updateOrder(
18 | \Magento\Sales\Model\Order $order
19 | ): \Magento\Sales\Model\Order {
20 | $payment = $order->getPayment();
21 | /** @var \Magento\Sales\Api\Data\OrderPaymentInterface $payment */
22 | $additionalInformation = $payment->getAdditionalInformation();
23 | $paymentState = $additionalInformation[
24 | AdditionalInformationInterface::AFTERPAY_PAYMENT_STATE
25 | ];
26 | if ($paymentState == PaymentStateInterface::CAPTURED) {
27 | $order->setState(\Magento\Sales\Model\Order::STATE_COMPLETE);
28 | $order->setStatus(\Magento\Sales\Model\Order::STATE_COMPLETE);
29 | }
30 | $this->orderRepository->save($order);
31 | return $order;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Test/Unit/Model/Url/UrlBuilderTest.php:
--------------------------------------------------------------------------------
1 | createMock(\Afterpay\Afterpay\Model\Url\UrlBuilder\UrlFactory::class);
16 | $urlFactoryMock->expects($this->once())
17 | ->method('create')
18 | ->willReturn($testUrl);
19 |
20 | $urlBuilder = new \Afterpay\Afterpay\Model\Url\UrlBuilder($urlFactoryMock);
21 | $url = $urlBuilder->build($testType, $path, $args);
22 |
23 | static::assertSame($testUrl . $expectedPath, $url);
24 | }
25 |
26 | public function argumentsReplacementDataProvider(): array
27 | {
28 | return [
29 | ['v2/payments/{orderId}/capture', ['orderId' => 12345], 'v2/payments/12345/capture'],
30 | ['v2/payments/auth', [], 'v2/payments/auth'],
31 | ['v2/payments/capture', ['unExistedArg' => 999], 'v2/payments/capture']
32 | ];
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Observer/Payment/DataAssignObserver.php:
--------------------------------------------------------------------------------
1 | readDataArgument($observer);
16 | $additionalData = $data->getData(\Magento\Quote\Api\Data\PaymentInterface::KEY_ADDITIONAL_DATA);
17 | if (!is_array($additionalData)) {
18 | return;
19 | }
20 |
21 | $paymentInfo = $this->readPaymentModelArgument($observer);
22 |
23 | foreach ($this->additionalInformationList as $additionalInformationKey) {
24 | if (isset($additionalData[$additionalInformationKey])) {
25 | $paymentInfo->setAdditionalInformation(
26 | $additionalInformationKey,
27 | $additionalData[$additionalInformationKey]
28 | );
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/AdaptPayments.php:
--------------------------------------------------------------------------------
1 | salesSetup = $salesSetup;
15 | }
16 |
17 | public function getAliases(): array
18 | {
19 | return [];
20 | }
21 |
22 | public static function getDependencies(): array
23 | {
24 | return [];
25 | }
26 |
27 | public function apply(): self
28 | {
29 | $this->salesSetup->getConnection()
30 | ->update(
31 | $this->salesSetup->getTable('sales_order_payment'),
32 | [
33 | 'method' => \Afterpay\Afterpay\Gateway\Config\Config::CODE,
34 | 'additional_information' => new \Zend_Db_Expr(
35 | 'replace(additional_information, "afterpay_payment_status", "afterpay_payment_state")'
36 | )
37 | ],
38 | ['method = ?' => static::METHOD_CODE]
39 | );
40 |
41 | return $this;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Model/Shipment/Express/ShippingListProvider.php:
--------------------------------------------------------------------------------
1 | createShippingOption = $createShippingOption;
15 | $this->shipmentEstimation = $shipmentEstimation;
16 | }
17 |
18 | public function provide(\Magento\Quote\Model\Quote $quote): array
19 | {
20 | $shippingMethods = $this->shipmentEstimation->estimateByExtendedAddress(
21 | $quote->getId(),
22 | $quote->getShippingAddress()
23 | );
24 | $shippingOptions = [];
25 | foreach ($shippingMethods as $shippingMethod) {
26 | if (!$shippingMethod->getAvailable()) {
27 | continue;
28 | }
29 | $shippingOption = $this->createShippingOption->create($quote, $shippingMethod);
30 | if ($shippingOption != null) {
31 | $shippingOptions[] = $shippingOption;
32 | }
33 | }
34 | return $shippingOptions;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Plugin/Block/Adminhtml/Order/Creditmemo/Create/Items.php:
--------------------------------------------------------------------------------
1 | layout = $layout;
15 | $this->checkPaymentMethod = $checkPaymentMethod;
16 | }
17 |
18 | /**
19 | * @return null
20 | */
21 | public function beforeToHtml(
22 | \Magento\Sales\Block\Adminhtml\Order\Creditmemo\Create\Items $creditmemoBlock
23 | ) {
24 | $creditmemo = $creditmemoBlock->getCreditmemo();
25 | $payment = $creditmemo->getOrder()->getPayment();
26 | if ($payment == null) {
27 | return null;
28 | }
29 |
30 | if ($this->checkPaymentMethod->isAfterPayMethod($payment)) {
31 | $this->layout->unsetChild(
32 | $creditmemoBlock->getNameInLayout(),
33 | !$creditmemo->canRefund() ? 'submit_button' : 'submit_offline'
34 | );
35 | }
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Gateway/Validator/Method/CurrencyValidator.php:
--------------------------------------------------------------------------------
1 | config = $config;
16 | $this->checkoutSession = $checkoutSession;
17 | parent::__construct($resultFactory);
18 | }
19 |
20 | public function validate(array $validationSubject): \Magento\Payment\Gateway\Validator\ResultInterface
21 | {
22 | $quote = $this->checkoutSession->getQuote();
23 | $currentCurrency = $quote->getStore()->getCurrentCurrencyCode();
24 | $allowedCurrencies = $this->config->getAllowedCurrencies();
25 | $cbtCurrencies = array_keys($this->config->getCbtCurrencyLimits());
26 |
27 | if (in_array($currentCurrency, array_merge($allowedCurrencies, $cbtCurrencies))) {
28 | return $this->createResult(true);
29 | }
30 |
31 | return $this->createResult(false);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Model/Order/CreditMemo/CaptureProcessor.php:
--------------------------------------------------------------------------------
1 | authCaptureCommand = $authCaptureCommand;
18 | $this->paymentDataObjectFactory = $paymentDataObjectFactory;
19 | }
20 |
21 | /**
22 | * @throws \Magento\Framework\Exception\PaymentException
23 | * @throws \Magento\Payment\Gateway\Command\CommandException
24 | */
25 | public function execute(float $amountToCapture, \Magento\Payment\Model\InfoInterface $payment): void
26 | {
27 | if ($amountToCapture > 0) {
28 | $this->authCaptureCommand->execute([
29 | 'amount' => $amountToCapture,
30 | 'payment' => $this->paymentDataObjectFactory->create($payment)
31 | ]);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Api/CheckoutManagementInterface.php:
--------------------------------------------------------------------------------
1 | config = $config;
14 | }
15 |
16 | public function check(string $currencyCode, ?float $amount = null): bool
17 | {
18 | $cbtCurrencies = $this->config->getCbtCurrencyLimits();
19 |
20 | if ($amount === null) {
21 | return key_exists($currencyCode, $cbtCurrencies);
22 | }
23 |
24 | return key_exists($currencyCode, $cbtCurrencies) && $cbtCurrencies[$currencyCode] > $amount;
25 | }
26 |
27 | public function checkByQuote(\Magento\Quote\Model\Quote $quote): bool
28 | {
29 | if ($this->canUseCurrentCurrency !== null) {
30 | return $this->canUseCurrentCurrency;
31 | }
32 |
33 | $currentCurrencyCode = $quote->getQuoteCurrencyCode() ?? $quote->getStore()->getCurrentCurrencyCode();
34 | $amount = (float) $quote->getGrandTotal();
35 | $this->canUseCurrentCurrency = $this->check($currentCurrencyCode, $amount);
36 |
37 | return $this->canUseCurrentCurrency;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Model/ResourceModel/Token.php:
--------------------------------------------------------------------------------
1 | _init('afterpay_tokens_log', TokenInterface::LOG_ID_FIELD);
16 | $this->_useIsObjectNew = true;
17 | }
18 |
19 | public function selectByToken(string $token): string
20 | {
21 | $connection = $this->getConnection();
22 | $select = $connection->select()
23 | ->from($this->getMainTable())
24 | ->where(TokenInterface::TOKEN_FIELD . ' = ?', $token);
25 |
26 | $result = $connection->fetchOne($select);
27 |
28 | return is_string($result) ? $result : '';
29 | }
30 |
31 | public function insertNewToken(int $orderId, string $token, ?string $expiryDate): int
32 | {
33 | return $this->getConnection()->insert(
34 | $this->getMainTable(),
35 | [
36 | TokenInterface::ORDER_ID_FIELD => $orderId,
37 | TokenInterface::TOKEN_FIELD => $token,
38 | TokenInterface::EXPIRATION_DATE_FIELD => $expiryDate,
39 | ]
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/container/cta/cta.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'afterpayBaseContainer',
3 | 'ko',
4 | 'Magento_Catalog/js/price-utils',
5 | 'Afterpay_Afterpay/js/service/container/cta/modal-options-updater'
6 | ], function (Component, ko, priceUtils, modalOptionsUpdater) {
7 | 'use strict';
8 |
9 | return Component.extend({
10 | defaults: {
11 | dataIsEligible: "true",
12 | dataCbtEnabledString: "false",
13 | pageType: "product"
14 | },
15 | initialize: function () {
16 | const res = this._super();
17 | this.dataShowLowerLimit = this._getStringBool(this.dataShowLowerLimit);
18 | this.dataCbtEnabledString = this._getStringBool(this.dataCbtEnabled);
19 | return res;
20 | },
21 | initObservable: function () {
22 | const res = this._super();
23 | this.dataIsEligible = ko.computed(() => this._getStringBool(this.isProductAllowed()));
24 | this.dataAmount = ko.computed(() => this.isProductAllowed() ? priceUtils.formatPrice(this.currentPrice()) : "");
25 | return res;
26 | },
27 | onRendered: function () {
28 | if (this.id) {
29 | modalOptionsUpdater(this.id, {
30 | locale: this.dataLocale,
31 | cbtEnabled: this.dataCbtEnabled
32 | });
33 | }
34 | }
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/Button/LimitUpdate.php:
--------------------------------------------------------------------------------
1 | unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
15 | return parent::render($element);
16 | }
17 |
18 | /** @inheritDoc */
19 | protected function _prepareLayout()
20 | {
21 | parent::_prepareLayout();
22 |
23 | if (!$this->getTemplate()) {
24 | $this->setTemplate(static::TEMPLATE);
25 | }
26 |
27 | return $this;
28 | }
29 |
30 | /** @inheritDoc */
31 | public function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
32 | {
33 | $originalData = $element->getData('original_data');
34 | $this->addData(
35 | [
36 | 'button_label' => __($originalData['button_label']),
37 | 'button_url' => $this->getUrl($originalData['button_url']),
38 | 'html_id' => $element->getHtmlId(),
39 | ]
40 | );
41 | return $this->_toHtml();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Gateway/Validator/Method/NotAllowedProductsValidator.php:
--------------------------------------------------------------------------------
1 | notAllowedProductsProvider = $notAllowedProductsProvider;
15 | }
16 |
17 | public function validate(array $validationSubject): \Magento\Payment\Gateway\Validator\ResultInterface
18 | {
19 | /** @var \Magento\Quote\Model\Quote $quote */
20 | $quote = $validationSubject['quote'];
21 |
22 | $disallowedProductsIds = $this->notAllowedProductsProvider->provideIds((int)$quote->getStoreId());
23 | $disallowedProductsIdsAsKeys = array_flip($disallowedProductsIds);
24 |
25 | foreach ($quote->getItems() ?? [] as $item) {
26 | if (isset($disallowedProductsIdsAsKeys[(int)$item->getProductId()])) {
27 | return $this->createResult(false);
28 | }
29 | }
30 | return $this->createResult(true);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Model/Order/CreditMemo/PaymentUpdater.php:
--------------------------------------------------------------------------------
1 | getPaymentDataCommand = $getPaymentDataCommand;
17 | $this->paymentDataObjectFactory = $paymentDataObjectFactory;
18 | $this->orderPaymentRepository = $orderPaymentRepository;
19 | }
20 |
21 | public function updatePayment(
22 | \Magento\Payment\Model\InfoInterface $payment
23 | ): \Magento\Sales\Api\Data\OrderPaymentInterface {
24 | $this->getPaymentDataCommand->execute([
25 | 'payment' => $this->paymentDataObjectFactory->create($payment)
26 | ]);
27 | /** @var \Magento\Sales\Api\Data\OrderPaymentInterface $payment */
28 | $this->orderPaymentRepository->save($payment);
29 | return $payment;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Plugin/Block/Adminhtml/CustomerBalance/Order/Creditmemo/Controls.php:
--------------------------------------------------------------------------------
1 | registry = $registry;
15 | $this->checkPaymentMethod = $checkPaymentMethod;
16 | }
17 |
18 | /**
19 | * @param \Magento\CustomerBalance\Block\Adminhtml\Sales\Order\Creditmemo\Controls $subject
20 | * @param callable $proceed
21 | *
22 | * @return false
23 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
24 | */
25 | public function aroundCanRefundToCustomerBalance($subject, callable $proceed)
26 | {
27 | $creditmemo = $this->registry->registry('current_creditmemo');
28 | if (!$creditmemo) {
29 | return $proceed();
30 | }
31 | $payment = $creditmemo->getOrder()->getPayment();
32 | if (!$payment) {
33 | return $proceed();
34 | }
35 | if ($this->checkPaymentMethod->isAfterPayMethod($payment)) {
36 | return false;
37 | }
38 | return $proceed();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/etc/db_schema.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/view/frontend/templates/express-checkout/minicart-headless.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
7 | $jsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/express-checkout/minicart/headless.js');
8 | $externalJsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/express-checkout/minicart/express-checkout-minicart-widget.js');
9 | ?>
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Observer/AuthCaptureBeforeShipment.php:
--------------------------------------------------------------------------------
1 | shipmentCaptureProcessor = $shipmentCaptureProcessor;
15 | $this->checkPaymentMethod = $checkPaymentMethod;
16 | }
17 |
18 | /**
19 | * @throws \Magento\Framework\Exception\PaymentException
20 | * @throws \Magento\Payment\Gateway\Command\CommandException
21 | */
22 | public function execute(\Magento\Framework\Event\Observer $observer): void
23 | {
24 | /** @var \Magento\Sales\Model\Order\Shipment $shipment */
25 | $shipment = $observer->getEvent()->getData('shipment');
26 |
27 | /** @var \Magento\Sales\Model\Order\Payment $paymentInfo */
28 | $paymentInfo = $shipment->getOrder()->getPayment();
29 |
30 | if (!$this->checkPaymentMethod->isAfterPayMethod($paymentInfo)) {
31 | return;
32 | }
33 |
34 | $this->shipmentCaptureProcessor->execute($shipment);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Api/Data/CheckoutInterface.php:
--------------------------------------------------------------------------------
1 | config = $config;
13 | }
14 |
15 | public function handle(array $handlingSubject, array $response): void
16 | {
17 | $websiteId = (int)$handlingSubject['websiteId'];
18 | $merchantCountry = $this->config->getMerchantCountry(
19 | \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE,
20 | $websiteId
21 | );
22 | $specificCountries = [];
23 | if ($merchantCountry != null) {
24 | $specificCountries[] = $merchantCountry;
25 | }
26 | $specificCountries = array_intersect($this->config->getAllowedCountries($websiteId), $specificCountries);
27 | if (isset($response['CBT']['enabled']) &&
28 | isset($response['CBT']['countries']) &&
29 | is_array($response['CBT']['countries']) &&
30 | count($specificCountries) > 0
31 | ) {
32 | $specificCountries = array_unique(array_merge($specificCountries, $response['CBT']['countries']));
33 | }
34 | $this->config->setSpecificCountries(implode(",", $specificCountries), $websiteId);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Plugin/Quote/CheckoutManagement.php:
--------------------------------------------------------------------------------
1 | quoteRepository = $quoteRepository;
25 | $this->checkoutSession = $checkoutSession;
26 | $this->paymentMethodChecker = $paymentMethodChecker;
27 | }
28 |
29 | public function beforePlaceOrder(CartManagementInterface $subject, $cartId, ?PaymentInterface $paymentMethod = null)
30 | {
31 | $quote = $this->quoteRepository->getActive($cartId);
32 | $payment = $quote->getPayment();
33 | if ($this->paymentMethodChecker->isAfterPayMethod($payment) && !$this->checkoutSession->getAfterpayRedirect()) {
34 | throw new LocalizedException(__('You cannot use the chosen payment method.'));
35 | }
36 |
37 | return [$cartId, $paymentMethod];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Gateway/Request/PaymentAction/CaptureDataBuilder.php:
--------------------------------------------------------------------------------
1 | getPayment();
11 | /** @var \Magento\Sales\Api\Data\OrderInterface $order */
12 | $order = $payment->getOrder();
13 |
14 | $isCBTCurrency = (bool) $payment->getAdditionalInformation(
15 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY
16 | );
17 | $cbtCurrency = $payment->getAdditionalInformation(
18 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_CBT_CURRENCY
19 | );
20 | $currencyCode = ($isCBTCurrency && $cbtCurrency) ? $cbtCurrency : $order->getBaseCurrencyCode();
21 |
22 | $token = $payment->getAdditionalInformation(\Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_TOKEN);
23 | $data = [
24 | 'storeId' => $order->getStoreId(),
25 | 'token' => $token
26 | ];
27 |
28 | if ($payment->getAdditionalInformation('afterpay_express')) {
29 | $data['amount'] = [
30 | 'amount' => \Magento\Payment\Gateway\Helper\SubjectReader::readAmount($buildSubject),
31 | 'currency' => $currencyCode
32 | ];
33 | }
34 |
35 | return $data;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/AfterpayPendingExceptionReviewOrderStatus.php:
--------------------------------------------------------------------------------
1 | statusFactory = $statusFactory;
19 | $this->statusResource = $statusResource;
20 | }
21 |
22 | public function getAliases(): array
23 | {
24 | return [];
25 | }
26 |
27 | public static function getDependencies(): array
28 | {
29 | return [];
30 | }
31 |
32 | public function apply(): self
33 | {
34 | $status = $this->statusFactory->create();
35 | $status->setData([
36 | 'status' => \Afterpay\Afterpay\Model\Payment\PaymentErrorProcessor::ORDER_STATUS_CODE,
37 | 'label' => 'Pending Exception Review (Afterpay)',
38 | ]);
39 |
40 | try {
41 | $this->statusResource->save($status);
42 | } catch (AlreadyExistsException $exception) {
43 | return $this;
44 | }
45 |
46 | $status->assignState('processing', false, false);
47 | return $this;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Gateway/Request/PaymentAction/AuthCaptureDataBuilder.php:
--------------------------------------------------------------------------------
1 | getPayment();
17 |
18 | $isCBTCurrency = (bool) $payment->getAdditionalInformation(
19 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY
20 | );
21 | $cbtCurrency = $payment->getAdditionalInformation(
22 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_CBT_CURRENCY
23 | );
24 | $currencyCode = ($isCBTCurrency && $cbtCurrency) ? $cbtCurrency : $paymentDO->getOrder()->getCurrencyCode();
25 |
26 | $afterpayOrderId = $payment->getAdditionalInformation(
27 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_ORDER_ID
28 | );
29 |
30 | return [
31 | 'storeId' => $paymentDO->getOrder()->getStoreId(),
32 | 'orderId' => $afterpayOrderId,
33 | 'amount' => [
34 | 'amount' => $this->formatPrice(SubjectReader::readAmount($buildSubject)),
35 | 'currency' => $currencyCode
36 | ]
37 | ];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Plugin/QuoteGraphQl/Cart/PlaceOrderPlugin.php:
--------------------------------------------------------------------------------
1 | placeOrderProcessor = $placeOrderProcessor;
22 | $this->validateCheckoutDataCommand = $validateCheckoutDataCommand;
23 | }
24 |
25 | public function aroundExecute(
26 | PlaceOrderModel $subject,
27 | callable $proceed,
28 | Quote $cart,
29 | string $maskedCartId,
30 | int $userId
31 | ): int {
32 | $payment = $cart->getPayment();
33 | if ($payment->getMethod() === Config::CODE) {
34 | $afterpayOrderToken = $payment->getAdditionalInformation(CheckoutInterface::AFTERPAY_TOKEN);
35 |
36 | return $this->placeOrderProcessor->execute($cart, $this->validateCheckoutDataCommand, $afterpayOrderToken);
37 | }
38 |
39 | return $proceed($cart, $maskedCartId, $userId);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ViewModel/Container/Cta/Lib.php:
--------------------------------------------------------------------------------
1 | storeManager = $storeManager;
22 | parent::__construct($config, $libUrlProvider, $containerConfigPath);
23 | }
24 |
25 | public function getMinTotalValue(): ?string
26 | {
27 | $currencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode();
28 | $cbtLimits = $this->config->getCbtCurrencyLimits();
29 | if (isset($cbtLimits[$currencyCode])) {
30 | return $cbtLimits[$currencyCode]['minimumAmount']['amount'] ?? '0';
31 | }
32 |
33 | return $this->config->getMinOrderTotal();
34 | }
35 |
36 | public function getMaxTotalValue(): ?string
37 | {
38 | $currencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode();
39 | $cbtLimits = $this->config->getCbtCurrencyLimits();
40 | if (isset($cbtLimits[$currencyCode])) {
41 | return $cbtLimits[$currencyCode]['maximumAmount']['amount'];
42 | }
43 |
44 | return $this->config->getMaxOrderTotal();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ViewModel/Container/Lib.php:
--------------------------------------------------------------------------------
1 | config = $config;
19 | $this->libUrlProvider = $libUrlProvider;
20 | $this->containerConfigPath = $containerConfigPath;
21 | }
22 |
23 | public function isContainerEnable(): bool
24 | {
25 | $isContainerConfigPathEnabled = true;
26 | if ($this->containerConfigPath !== null) {
27 | $isContainerConfigPathEnabled = (bool)$this->config->getByConfigPath($this->containerConfigPath);
28 | }
29 | return $isContainerConfigPathEnabled &&
30 | $this->config->getIsPaymentActive() &&
31 | $this->config->getMinOrderTotal() !== null &&
32 | $this->config->getMaxOrderTotal() !== null &&
33 | in_array($this->config->getMerchantCountry(), $this->config->getSpecificCountries());
34 | }
35 |
36 | public function getIsLibLoadedAlready(): bool
37 | {
38 | return $this->libUrlProvider->getIsLibGotten();
39 | }
40 |
41 | public function getAfterpayLib(): ?string
42 | {
43 | return $this->libUrlProvider->getAfterpayLib();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Gateway/Request/PaymentAction/RefundAndVoidDataBuilder.php:
--------------------------------------------------------------------------------
1 | getPayment()->getAdditionalInformation(
16 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_ORDER_ID
17 | );
18 |
19 | $isCBTCurrency = (bool) $paymentDO->getPayment()->getAdditionalInformation(
20 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY
21 | );
22 | $CBTCurrency = $paymentDO->getPayment()->getAdditionalInformation(
23 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_CBT_CURRENCY
24 | );
25 | $currencyCode = ($isCBTCurrency && $CBTCurrency) ? $CBTCurrency : $paymentDO->getOrder()->getCurrencyCode();
26 |
27 | $data = [
28 | 'storeId' => $paymentDO->getOrder()->getStoreId(),
29 | 'orderId' => $afterpayOrderId
30 | ];
31 | try {
32 | return array_merge($data, [
33 | 'amount' => [
34 | 'amount' => $this->formatPrice(SubjectReader::readAmount($buildSubject)),
35 | 'currency' => $currencyCode
36 | ]
37 | ]);
38 | } catch (\InvalidArgumentException $e) {
39 | return $data;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Plugin/Sales/Model/Service/CreditmemoService/AdjustmentAmountValidation.php:
--------------------------------------------------------------------------------
1 | getOrder();
25 | if (($creditmemo->getBaseAdjustmentPositive() != 0 || $creditmemo->getBaseAdjustmentNegative() != 0)
26 | && $order->getPayment()->getMethod() === Config::CODE
27 | && !in_array(
28 | $order->getPayment()->getAdditionalInformation(AdditionalInformationInterface::AFTERPAY_PAYMENT_STATE),
29 | self::ALLOWED_PAYMENT_STATES
30 | )) {
31 | throw new LocalizedException(__(
32 | 'You cannot use adjustments for a payment with a status'
33 | . ' that does not equal "CAPTURED" or "PARTIALLY_CAPTURED" for the current payment method.'
34 | ));
35 | }
36 |
37 | return [$creditmemo, $offlineRequested];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Model/Url/UrlBuilder/UrlFactory.php:
--------------------------------------------------------------------------------
1 | config = $config;
17 | $this->storeManager = $storeManager;
18 | $this->environments = $environments;
19 | }
20 |
21 | public function create(string $type, ?int $storeId = null, array $pathArgs = []): string
22 | {
23 | $apiMode = $this->config->getApiMode($pathArgs['websiteId'] ?? null);
24 | $item = $this->environments[$apiMode][$type] ?? false;
25 |
26 | if (!$item) {
27 | throw new \InvalidArgumentException('Afterpay environment url config is not found');
28 | }
29 | if (is_string($item)) {
30 | return $item;
31 | }
32 |
33 | if (isset($pathArgs['websiteId'])) {
34 | $currencyCode = $this->config->getMerchantCurrency(
35 | \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES,
36 | $pathArgs['websiteId']
37 | );
38 | }
39 | if (!isset($currencyCode)) {
40 | /** @var \Magento\Store\Model\Store $store */
41 | $store = $this->storeManager->getStore($storeId);
42 | $currencyCode = $store->getCurrentCurrencyCode();
43 | }
44 |
45 | return $item[$currencyCode] ?? $item['default'];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Model/CheckoutConfigProvider.php:
--------------------------------------------------------------------------------
1 | localeResolver = $localeResolver;
21 | $this->checkoutSession = $checkoutSession;
22 | $this->checkCBTCurrencyAvailability = $checkCBTCurrencyAvailability;
23 | $this->config = $config;
24 | }
25 |
26 | public function getConfig(): array
27 | {
28 | $quote = $this->checkoutSession->getQuote();
29 |
30 | return [
31 | 'payment' => [
32 | 'afterpay' => [
33 | 'locale' => $this->localeResolver->getLocale(),
34 | 'isCBTCurrency' => $this->checkCBTCurrencyAvailability->checkByQuote($quote),
35 | 'consumerLendingEnabled' => $this->config->getConsumerLendingEnabled(),
36 | 'consumerLendingMinimumAmount' => $this->config->getConsumerLendingMinAmount(),
37 | 'mpid' => $this->config->getPublicId(),
38 | ]
39 | ]
40 | ];
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/view/frontend/templates/express-checkout/cart-headless.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
7 | $jsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/express-checkout/cart/headless.js');
8 | $externalJsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/express-checkout/cart/express-checkout-cart-widget.js');
9 | ?>
10 |
11 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Gateway/Command/CommandPoolProxy.php:
--------------------------------------------------------------------------------
1 | commandPoolFactory = $commandPoolFactory;
19 | $this->config = $config;
20 | $this->commands = $commands;
21 | }
22 |
23 | protected function getCommandPool(): \Magento\Payment\Gateway\Command\CommandPoolInterface
24 | {
25 | if ($this->commandPool === null) {
26 | $paymentFlow = $this->config->getPaymentFlow();
27 |
28 | $this->commands['capture'] = $paymentFlow == \Afterpay\Afterpay\Model\Config\Source\PaymentFlow::DEFERRED ?
29 | $this->commands['auth_deferred'] :
30 | $this->commands['capture_immediate'];
31 |
32 | unset($this->commands['capture_immediate']);
33 | unset($this->commands['auth_deferred']);
34 |
35 | $this->commandPool = $this->commandPoolFactory->create(['commands' => $this->commands]);
36 | }
37 | return $this->commandPool;
38 | }
39 |
40 | public function get($commandCode): \Magento\Payment\Gateway\CommandInterface
41 | {
42 | return $this->getCommandPool()->get($commandCode);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/view/frontend/templates/express-checkout/headless.phtml:
--------------------------------------------------------------------------------
1 | getViewModel();
7 | $jsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/express-checkout/product/headless.js');
8 | $externalJsUrl = $block->getViewFileUrl('Afterpay_Afterpay::js/view/container/express-checkout/product/express-checkout-widget.js');
9 | ?>
10 |
11 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Model/GraphQl/Resolver/AfterpayConfigMiniCart.php:
--------------------------------------------------------------------------------
1 | config->getIsEnableMiniCartHeadless($websiteId);
36 | $result['is_enabled_ec_minicart_headless'] = $this->config->getIsEnableMiniCartHeadless($websiteId);
37 | $result['placement_wrapper'] = $this->config->getMiniCartPlacementContainerSelector($websiteId);
38 | $result['placement_after_selector'] = $this->config->getMiniCartPlacementAfterSelector($websiteId);
39 | $result['price_selector'] = $this->config->getMiniCartPlacementPriceSelector($websiteId);
40 |
41 | return $result;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/view/frontend/web/css/afterpay-checkout.less:
--------------------------------------------------------------------------------
1 | .afterpay.payment-method {
2 | .payment-method-note {
3 | &.afterpay-checkout-note {
4 | font-size: 1.2rem;
5 | color: #a0a0a0;
6 | text-align: center;
7 | }
8 | }
9 | .payment-method-content {
10 | overflow: hidden;
11 | padding-right: 22px;
12 | }
13 | .payment-icon {
14 | height: 40px;
15 | width: auto;
16 | }
17 | .actions-toolbar-wraper {
18 | margin-top: 1rem;
19 | text-align: left;
20 | background-color: #f6f7f9;
21 | display: inline-block;
22 | padding: 15px;
23 | .terms-conditions {
24 | width: 65%;
25 | float: left;
26 | }
27 | .actions-toolbar {
28 | float: right;
29 | .action.primary.checkout {
30 | line-height: 2.2rem;
31 | padding: 14px 17px;
32 | font-size: 1.8rem;
33 | }
34 | }
35 | }
36 | .afterpay-green {
37 | background: #00d632;
38 | border: #00d632;
39 | }
40 | }
41 |
42 | @media only screen and (max-width : 1150px) {
43 | .afterpay.payment-method {
44 | .actions-toolbar-wraper {
45 | .terms-conditions {
46 | width: 57%;
47 | }
48 | }
49 | }
50 | }
51 |
52 | @media only screen and (max-width : 993px) {
53 | .afterpay.payment-method {
54 | .actions-toolbar-wraper {
55 | .terms-conditions {
56 | width: 100%;
57 | }
58 | .actions-toolbar {
59 | width: 100%;
60 | margin-top: 1.5rem;
61 | .primary, .action.primary.checkout {
62 | width: 100%;
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Gateway/Request/ExpressCheckoutDataBuilder.php:
--------------------------------------------------------------------------------
1 | getQuoteCurrencyCode();
12 | $isCBTCurrencyAvailable = $this->checkCBTCurrencyAvailability->checkByQuote($quote);
13 | $amount = $isCBTCurrencyAvailable ? $quote->getGrandTotal() : $quote->getBaseGrandTotal();
14 | $currencyCode = $isCBTCurrencyAvailable ? $currentCurrencyCode : $quote->getBaseCurrencyCode();
15 | $popupOriginUrl = $buildSubject['popup_origin_url'];
16 |
17 | $lastSelectedShippingRate = null;
18 | if ($quote->getShippingAddress() && $quote->getShippingAddress()->getShippingMethod()) {
19 | $lastSelectedShippingRate = $quote->getShippingAddress()->getShippingMethod();
20 | }
21 |
22 | $data = [
23 | 'mode' => 'express',
24 | 'storeId' => $quote->getStoreId(),
25 | 'amount' => [
26 | 'amount' => $this->formatPrice($amount),
27 | 'currency' => $currencyCode
28 | ],
29 | 'merchant' => [
30 | 'popupOriginUrl' => $popupOriginUrl
31 | ],
32 | 'items' => $this->getItems($quote),
33 | 'merchantReference' => $quote->getReservedOrderId(),
34 | 'shippingOptionIdentifier' => $lastSelectedShippingRate
35 | ];
36 |
37 | if ($discounts = $this->getDiscounts($quote)) {
38 | $data['discounts'] = $discounts;
39 | }
40 |
41 | return $data;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Model/Log/Method/Logger.php:
--------------------------------------------------------------------------------
1 | forceDebug = $forceDebug;
14 | return $this;
15 | }
16 |
17 | /**
18 | * @inheritDoc
19 | */
20 | public function debug(array $data, ?array $maskKeys = null, $forceDebug = null)
21 | {
22 | if ($forceDebug === null) {
23 | $forceDebug = $this->forceDebug ? true : null;
24 | }
25 | parent::debug($data, $maskKeys, $forceDebug);
26 | }
27 |
28 | protected function filterDebugData(
29 | array $debugData,
30 | array $debugReplacePrivateDataKeys,
31 | bool $maskAll = false
32 | ): array {
33 | $debugReplacePrivateDataKeys = array_map('strtolower', $debugReplacePrivateDataKeys);
34 |
35 | foreach (array_keys($debugData) as $key) {
36 | $isKeyToReplace = in_array(strtolower((string)$key), $debugReplacePrivateDataKeys);
37 | $isMasked = !is_array($debugData[$key]) && ($isKeyToReplace || $maskAll);
38 | if ($isMasked) {
39 | $debugData[$key] = self::DEBUG_KEYS_MASK;
40 | } elseif ($isKeyToReplace && is_array($debugData[$key])) {
41 | $debugData[$key] = $this->filterDebugData(
42 | $debugData[$key],
43 | $debugReplacePrivateDataKeys,
44 | true
45 | );
46 | } elseif (is_array($debugData[$key])) {
47 | $debugData[$key] = $this->filterDebugData($debugData[$key], $debugReplacePrivateDataKeys);
48 | }
49 | }
50 | return $debugData;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Cron/MerchantConfigurationUpdater.php:
--------------------------------------------------------------------------------
1 | merchantConfigurationCommand = $merchantConfigurationCommand;
23 | $this->storeManager = $storeManager;
24 | $this->config = $config;
25 | $this->typeList = $typeList;
26 | $this->logger = $logger;
27 | }
28 |
29 | public function execute(): void
30 | {
31 | $websites = $this->storeManager->getWebsites(true);
32 | foreach ($websites as $website) {
33 | $websiteId = (int)$website->getId();
34 | if ($this->config->getIsPaymentActive($websiteId)) {
35 | try {
36 | $this->merchantConfigurationCommand->execute([
37 | 'websiteId' => $websiteId
38 | ]);
39 | $this->typeList->cleanType(\Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER);
40 | } catch (\Exception $e) {
41 | $this->logger->critical($e->getMessage());
42 | }
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/container/cta/pdp/cta.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'afterpayBaseContainer',
3 | 'ko',
4 | 'Magento_Catalog/js/price-utils',
5 | 'Afterpay_Afterpay/js/service/container/cta/modal-options-updater'
6 | ], function (Component, ko, priceUtils, modalOptionsUpdater) {
7 | 'use strict';
8 |
9 | return Component.extend({
10 | defaults: {
11 | dataIsEligible: "true",
12 | dataCbtEnabledString: "false",
13 | pageType: "product",
14 | dataAmountSelector: ".product-info-main .price-final_price .price-wrapper .price",
15 | dataAmountSelectorBundle: "#bundleSummary .price-as-configured .price"
16 | },
17 | initialize: function () {
18 | const res = this._super();
19 | if (!!document.querySelector(this.dataAmountSelectorBundle)) {
20 | this.dataAmountSelector = this.dataAmountSelectorBundle;
21 | }
22 | this.dataShowLowerLimit = this._getStringBool(this.dataShowLowerLimit);
23 | this.dataCbtEnabledString = this._getStringBool(this.dataCbtEnabled);
24 | return res;
25 | },
26 | initObservable: function () {
27 | const res = this._super();
28 | this.dataIsEligible = ko.computed(() => this._getStringBool(this.isProductAllowed()));
29 | this.dataAmount = ko.computed(() => this.isProductAllowed() ? priceUtils.formatPrice(this.currentPrice()) : "");
30 | return res;
31 | },
32 | _getIsVisible: function () {
33 | return true;
34 | },
35 | onRendered: function () {
36 | if (this.id) {
37 | modalOptionsUpdater(this.id, {
38 | locale: this.dataLocale,
39 | cbtEnabled: this.dataCbtEnabled
40 | });
41 | }
42 | }
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/UpdateCbtInfoPatch.php:
--------------------------------------------------------------------------------
1 | merchantConfigurationCommand = $merchantConfigurationCommand;
25 | $this->storeManager = $storeManager;
26 | $this->typeList = $typeList;
27 | $this->logger = $logger;
28 | }
29 |
30 | public function getAliases(): array
31 | {
32 | return [];
33 | }
34 |
35 | public static function getDependencies(): array
36 | {
37 | return [];
38 | }
39 |
40 | public function apply()
41 | {
42 | $websites = $this->storeManager->getWebsites(true);
43 | foreach ($websites as $website) {
44 | $websiteId = (int)$website->getId();
45 | try {
46 | $this->merchantConfigurationCommand->execute([
47 | 'websiteId' => $websiteId
48 | ]);
49 | $this->typeList->cleanType(\Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER);
50 | } catch (\Exception $e) {
51 | $this->logger->critical($e);
52 | }
53 | }
54 |
55 | return $this;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/container/container.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'uiComponent',
3 | 'ko',
4 | 'Afterpay_Afterpay/js/model/container/container-model-holder',
5 | ], function (Component, ko, containerModelHolder) {
6 | 'use strict';
7 |
8 | return Component.extend({
9 | defaults: {
10 | currentProductsModelId: "none",
11 | modelContainerId: "none",
12 | currentPrice: 0,
13 | isProductAllowed: "true",
14 | isVisible: false,
15 | notAllowedProducts: []
16 | },
17 | initialize: function () {
18 | const res = this._super();
19 | this.dataShowLowerLimit = this._getStringBool(this.dataShowLowerLimit);
20 | return res;
21 | },
22 | initObservable: function () {
23 | const res = this._super();
24 | this.containerModel = containerModelHolder.getModel(this.modelContainerId);
25 | this.isProductAllowed = ko.computed(this._getIsAllProductsAllowed.bind(this));
26 | this.currentPrice = ko.computed(() => this.containerModel.getPrice());
27 | this.isVisible = ko.computed(this._getIsVisible.bind(this));
28 | return res;
29 | },
30 | _getStringBool: function (value) {
31 | return value ? "true" : "false";
32 | },
33 | _getIsVisible: function () {
34 | return this.currentPrice() !== 0;
35 | },
36 | _getIsAllProductsAllowed: function () {
37 | return this._getIsAllProductsInArrayAllowed(this.containerModel.getCurrentProductsIds());
38 | },
39 | _getIsAllProductsInArrayAllowed: function (array) {
40 | return array.reduce(
41 | (isAllowed, productId) => isAllowed && !this.notAllowedProducts.includes(productId.toString()),
42 | true
43 | );
44 | }
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/Setup/Patch/Data/UpdateMPIDInfoPatch.php:
--------------------------------------------------------------------------------
1 | merchantConfigurationCommand = $merchantConfigurationCommand;
25 | $this->storeManager = $storeManager;
26 | $this->typeList = $typeList;
27 | $this->logger = $logger;
28 | }
29 |
30 | public function getAliases(): array
31 | {
32 | return [];
33 | }
34 |
35 | public static function getDependencies(): array
36 | {
37 | return [];
38 | }
39 |
40 | public function apply()
41 | {
42 | $websites = $this->storeManager->getWebsites(true);
43 | foreach ($websites as $website) {
44 | $websiteId = (int)$website->getId();
45 | try {
46 | $this->merchantConfigurationCommand->execute([
47 | 'websiteId' => $websiteId
48 | ]);
49 | $this->typeList->cleanType(\Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER);
50 | } catch (\Exception $e) {
51 | $this->logger->critical($e);
52 | }
53 | }
54 |
55 | return $this;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/view/frontend/web/js/service/container/cart/abstract-data-retriever.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'uiComponent',
3 | 'ko',
4 | 'Afterpay_Afterpay/js/model/container/container-model-holder',
5 | 'Magento_Customer/js/customer-data'
6 | ], function (Component, ko, containerModelHolder, customerData) {
7 | return Component.extend({
8 | defaults: {
9 | modelContainerId: "none",
10 | isVisible: false,
11 | dataAmount: "0",
12 | cartModel: customerData.get('cart')
13 | },
14 | initialize: function () {
15 | const res = this._super();
16 | this._updateContainerModel(this.modelWithPrice());
17 | this._cartUpdate(this.cartModel());
18 | return res;
19 | },
20 | _updateContainerModel: function (modelWithPrice) {
21 | const containerModel = containerModelHolder.getModel(this.modelContainerId);
22 | if (modelWithPrice[this.priceKey] && parseFloat(modelWithPrice[this.priceKey]) > 0) {
23 | containerModel.setPrice(modelWithPrice[this.priceKey]);
24 | } else {
25 | containerModel.setPrice(0);
26 | }
27 | },
28 | _cartUpdate: function (newCart) {
29 | const containerModel = containerModelHolder.getModel(this.modelContainerId);
30 | if (newCart && Array.isArray(newCart.items)) {
31 | containerModel.setCurrentProductsIds(newCart.items.map((item) => item.product_id));
32 | containerModel.setIsVirtual(newCart.items.reduce((isVirtual, item) => isVirtual && item.is_virtual, true));
33 | }
34 | },
35 | initObservable: function () {
36 | const res = this._super();
37 | this.modelWithPrice.subscribe(this._updateContainerModel, this)
38 | this.cartModel.subscribe(this._cartUpdate, this)
39 | return res;
40 | }
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/view/frontend/web/js/service/container/pricebox-widget-mixin.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery',
3 | "Afterpay_Afterpay/js/model/container/container-model-holder"
4 | ], function ($, containerModelHolder) {
5 | 'use strict';
6 | const containerModel = containerModelHolder.getModel("afterpay-pdp-container");
7 | const priceBoxWidget = {
8 | _checkIsFinalPriceDefined: function () {
9 | return !!(
10 | this.cache.displayPrices &&
11 | this.cache.displayPrices.finalPrice &&
12 | this.cache.displayPrices.finalPrice.formatted
13 | );
14 | },
15 | updatePrice: function (newPrices) {
16 | const res = this._super(newPrices);
17 | let amountSelector = ".product-info-main .price-final_price .price-wrapper .price";
18 | const amountSelectorBundle = "#bundleSummary .price-as-configured .price";
19 | let price = 0;
20 | if (this._checkIsFinalPriceDefined()) {
21 | price = this.cache.displayPrices.finalPrice.amount;
22 | } else {
23 | if (!!document.querySelector(amountSelectorBundle)) {
24 | amountSelector = amountSelectorBundle;
25 | }
26 | let priceData = $(amountSelector).first().text();
27 | price = parseFloat(priceData.replace(/[^0-9.]/g, ''));
28 | }
29 |
30 | if (this.element.closest('.product-info-main').length > 0 ||
31 | this.element.closest('.bundle-options-container').length > 0) {
32 | containerModel.setCurrentProductId(this.element.data('productId'));
33 | containerModel.setPrice(price);
34 | }
35 |
36 | return res;
37 | }
38 | };
39 | return function (targetWidget) {
40 | $.widget('mage.priceBox', targetWidget, priceBoxWidget);
41 |
42 | return $.mage.priceBox;
43 | };
44 | });
45 |
--------------------------------------------------------------------------------
/ViewModel/Container/Cta/Cta.php:
--------------------------------------------------------------------------------
1 | localeResolver = $localeResolver;
20 | }
21 |
22 | public function updateJsLayout(
23 | string $jsLayoutJson,
24 | bool $remove = false,
25 | string $containerNodeName = 'afterpay.cta',
26 | array $config = []
27 | ): string {
28 | if (!$remove && $this->isContainerEnable()) {
29 | $store = $this->storeManager->getStore();
30 | $config['dataCurrency'] = $store->getCurrentCurrencyCode();
31 | $config['dataLocale'] = $this->localeResolver->getLocale();
32 | $config['dataShowLowerLimit'] = $this->config->getMinOrderTotal() >= 1;
33 | $config['dataCbtEnabled'] = count($this->config->getSpecificCountries()) > 1;
34 | $config['dataMPID'] = $this->config->getPublicId();
35 | $config['dataPlatform'] = 'Magento';
36 | $config['dataAmountSelector'] = $this->config->getPdpPlacementPriceSelector();
37 | $config['dataAmountSelectorBundle'] = $this->config->getPdpPlacementPriceSelectorBundle();
38 | }
39 | return parent::updateJsLayout($jsLayoutJson, $remove, $containerNodeName, $config);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/Fieldset/AllowedByCountry.php:
--------------------------------------------------------------------------------
1 | afterpay = $afterpay;
23 | $this->config = $config;
24 | $this->allowedCountriesConfigPath = $allowedCountriesConfigPath;
25 | }
26 |
27 | public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element): string
28 | {
29 | $allowedMerchantCountries = explode(',', $this->afterpay->getConfigData($this->allowedCountriesConfigPath));
30 | if (in_array($this->getMerchantCountry(), $allowedMerchantCountries)) {
31 | return parent::render($element);
32 | }
33 | return '';
34 | }
35 |
36 | private function getMerchantCountry(): ?string
37 | {
38 | /** @var \Magento\Config\Block\System\Config\Form $fieldSetForm */
39 | $fieldSetForm = $this->getForm();
40 | $scope = $fieldSetForm->getScope();
41 | $scopeCode = $fieldSetForm->getScopeCode();
42 |
43 | return $this->config->getMerchantCountry($scope, (int)$scopeCode);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/view/frontend/web/css/afterpay-express-checkout.less:
--------------------------------------------------------------------------------
1 | .afterpay-express-button, button.afterpay-express-button:hover {
2 | background-image: none;
3 | background: #000;
4 | border: 1px solid #000;
5 | color: #ffffff;
6 | cursor: pointer;
7 | display: inline-block;
8 | float: none;
9 | width: 267px;
10 | max-width: 100%;
11 | margin-top: 10px;
12 |
13 | }
14 | .afterpay-express-checkout-minicart-wraper {
15 | margin: 0 10px 15px;
16 | position: relative;
17 | z-index: 9;
18 | .afterpay-express-button, button.afterpay-express-button:hover {
19 | width: 100%;
20 | cursor: pointer;
21 | text-align: center;
22 | }
23 | }
24 |
25 | .headless-afterpay-ec .afterpay-express-button,
26 | .headless-afterpay-ec button.afterpay-express-button:hover,
27 | .headless-afterpay-ec .afterpay-express-button-minicart,
28 | .headless-afterpay-ec button.afterpay-express-button-minicart:hover {
29 | background-image: none;
30 | background: #000;
31 | border: 1px solid #000;
32 | color: #ffffff;
33 | cursor: pointer;
34 | display: inline-block;
35 | float: none;
36 | width: 267px;
37 | max-width: 100%;
38 | margin-top: 10px;
39 | }
40 |
41 | .headless-afterpay-ec .afterpay-express-button-minicart,
42 | .headless-afterpay-ec button.afterpay-express-button-minicart:hover {
43 | margin: 0 10px;
44 | width: 100%;
45 | max-width: 328px;
46 | padding: 2px 15px;
47 | }
48 |
49 | .headless-afterpay-ec .afterpay-express-button-cart,
50 | .headless-afterpay-ec button.afterpay-express-button-cart:hover {
51 | background: #000;
52 | border: none;
53 | }
54 |
55 | .headless-afterpay-ec.hidden {
56 | display: none;
57 | }
58 |
59 | .checkout-onepage-success .success.message {
60 | display: none;
61 | }
62 |
63 | .checkout-onepage-success .success.message.show-message {
64 | display: block;
65 | }
66 |
67 | .hyva_checkout-index-index #payment-method-option-afterpay {
68 | display: none;
69 | }
70 |
--------------------------------------------------------------------------------
/Plugin/Checkout/Block/Cart/Sidebar.php:
--------------------------------------------------------------------------------
1 | ctaContainerViewModel = $ctaContainerViewModel;
19 | $this->config = $config;
20 | $this->expressCheckoutViewModel = $expressCheckoutViewModel;
21 | }
22 |
23 | /**
24 | * @param string $result
25 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
26 | */
27 | public function afterGetJsLayout(\Magento\Checkout\Block\Cart\Sidebar $sidebar, $result): string
28 | {
29 | if (is_string($result) &&
30 | $this->config->getIsPaymentActive() &&
31 | $this->config->getMinOrderTotal() !== null &&
32 | $this->config->getMaxOrderTotal() !== null
33 | ) {
34 | $result = $this->ctaContainerViewModel->updateJsLayout(
35 | $result,
36 | !($this->config->getIsEnableCtaMiniCart()
37 | && $this->ctaContainerViewModel->isContainerEnable()
38 | && !$this->config->getIsEnableMiniCartHeadless())
39 | );
40 | $result = $this->expressCheckoutViewModel->updateJsLayout(
41 | $result,
42 | !($this->config->getIsEnableExpressCheckoutMiniCart() &&
43 | $this->expressCheckoutViewModel->isContainerEnable()
44 | && !$this->config->getIsEnableMiniCartHeadless())
45 | );
46 | }
47 |
48 | return $result;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ViewModel/Container/Cta/Headless.php:
--------------------------------------------------------------------------------
1 | registry = $registry;
34 | $this->localeResolver = $localeResolver;
35 | $this->checkoutSession = $checkoutSession;
36 | }
37 |
38 | public function getProductSku(): string
39 | {
40 | return $this->registry->registry('current_product')->getSku();
41 | }
42 |
43 | public function getStoreId(): string
44 | {
45 | return (string)$this->storeManager->getStore()->getId();
46 | }
47 |
48 | public function getCurrency(): string
49 | {
50 | return $this->storeManager->getStore()->getCurrentCurrencyCode();
51 | }
52 |
53 | public function getLocale(): string
54 | {
55 | return $this->localeResolver->getLocale();
56 | }
57 |
58 | public function getCartId(): string
59 | {
60 | return (string)$this->checkoutSession->getQuoteId();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/MerchantConfiguration/Update.php:
--------------------------------------------------------------------------------
1 | resultJsonFactory = $resultJsonFactory;
19 | $this->messageManager = $messageManager;
20 | $this->request = $request;
21 | $this->merchantConfigurationCommand = $merchantConfigurationCommand;
22 | }
23 |
24 | public function execute()
25 | {
26 | $websiteId = $this->request->getParam('websiteId');
27 | try {
28 | $this->merchantConfigurationCommand->execute(
29 | [
30 | "websiteId" => (int)$websiteId
31 | ]
32 | );
33 | $this->messageManager->addSuccessMessage(
34 | (string)__('Afterpay merchant configuration fetching is success.')
35 | );
36 | } catch (\Magento\Payment\Gateway\Command\CommandException $e) {
37 | $this->messageManager->addWarningMessage($e->getMessage());
38 | } catch (\Exception $e) {
39 | $this->messageManager->addErrorMessage(
40 | (string)__('Afterpay merchant configuration fetching is failed. See logs.')
41 | );
42 | }
43 | return $this->resultJsonFactory->create()->setData([
44 | 'done' => true
45 | ]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Observer/AuthCaptureBeforeCompleteOrder.php:
--------------------------------------------------------------------------------
1 | authCaptureCommand = $authCaptureCommand;
26 | $this->paymentDataObjectFactory = $paymentDataObjectFactory;
27 | }
28 |
29 | /**
30 | * Observer that captures the remaining amount when the order status is complete.
31 | *
32 | * @throws PaymentException
33 | * @throws CommandException
34 | */
35 | public function execute(Observer $observer): void
36 | {
37 | /** @var Order $order */
38 | $order = $observer->getEvent()->getOrder();
39 | $payment = $order->getPayment();
40 |
41 | if (!$payment->getMethod() == Config::CODE || $order->getStatus() !== Order::STATE_COMPLETE) {
42 | return;
43 | }
44 |
45 | $openToCapture = $payment->getAdditionalInformation(
46 | AdditionalInformationInterface::AFTERPAY_OPEN_TO_CAPTURE_AMOUNT
47 | );
48 |
49 | if ($openToCapture > 0) {
50 | $this->authCaptureCommand->execute([
51 | 'amount' => $openToCapture,
52 | 'payment' => $this->paymentDataObjectFactory->create($payment)
53 | ]);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Gateway/Command/RefundAndVoidCommand.php:
--------------------------------------------------------------------------------
1 | refundCommand = $refundCommand;
19 | $this->voidCommand = $voidCommand;
20 | $this->creditMemoAmountProcessor = $creditMemoAmountProcessor;
21 | }
22 |
23 | public function execute(array $commandSubject): ?\Magento\Payment\Gateway\Command\ResultInterface
24 | {
25 | $paymentDO = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($commandSubject);
26 |
27 | /** @var \Magento\Sales\Model\Order\Payment $payment */
28 | $payment = $paymentDO->getPayment();
29 |
30 | [$amountToRefund, $amountToVoid] = $this->creditMemoAmountProcessor->process($payment);
31 |
32 | $refundCommandSubject = array_merge(
33 | $commandSubject,
34 | ['amount' => $amountToRefund]
35 | );
36 | $voidCommandSubject = array_merge(
37 | $commandSubject,
38 | ['amount' => $amountToVoid]
39 | );
40 |
41 | if (\Magento\Payment\Gateway\Helper\SubjectReader::readAmount($refundCommandSubject) > 0) {
42 | $this->refundCommand->execute($refundCommandSubject);
43 | }
44 | if (\Magento\Payment\Gateway\Helper\SubjectReader::readAmount($voidCommandSubject) > 0) {
45 | $this->voidCommand->execute($voidCommandSubject);
46 | }
47 |
48 | return null;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ViewModel/Container/ExpressCheckout/Headless.php:
--------------------------------------------------------------------------------
1 | checkoutSession = $checkoutSession;
32 | $this->registry = $registry;
33 | }
34 |
35 | public function getProductSku(): string
36 | {
37 | return $this->registry->registry('current_product')->getSku();
38 | }
39 |
40 | public function getStoreId(): string
41 | {
42 | return (string)$this->storeManager->getStore()->getId();
43 | }
44 |
45 | public function getCartId(): string
46 | {
47 | return (string)$this->checkoutSession->getQuoteId();
48 | }
49 |
50 | public function getImageurl(): string
51 | {
52 | $urlPrefix = $this->config->getApiMode() === ApiMode::SANDBOX ? 'static.sandbox' : 'static';
53 | $localePart = str_replace('_', '-', $this->localeResolver->getLocale());
54 |
55 | return "https://$urlPrefix.afterpay.com/$localePart/integration/button/checkout-with-afterpay/white-on-black.svg";
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/payment/info/terms-and-conditions.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'uiComponent',
3 | 'Magento_Checkout/js/model/totals',
4 | 'mage/translate'
5 | ], function (
6 | Component,
7 | totals,
8 | $t
9 | ) {
10 | 'use strict';
11 |
12 | return Component.extend({
13 | getTermsText: function () {
14 | let afterpayTermsText = '';
15 | switch(totals.totals().quote_currency_code){
16 | case 'USD':
17 | afterpayTermsText = $t('You will be redirected to the $Afterpay website to fill out your payment information. You will be redirected back to our site to complete your order.');
18 | break
19 | case 'CAD':
20 | afterpayTermsText = $t('You will be redirected to the Afterpay website to fill out your payment information. You will be redirected back to our site to complete your order.');
21 | break;
22 | default:
23 | afterpayTermsText = $t('You will be redirected to the Afterpay website when you proceed to checkout.');
24 | }
25 |
26 | return afterpayTermsText;
27 | },
28 | getTermsLink: function () {
29 | let afterpayCheckoutTermsLink = '';
30 | switch(totals.totals().quote_currency_code){
31 | case 'USD':
32 | afterpayCheckoutTermsLink = "https://www.afterpay.com/en-US/installment-agreement";
33 | break;
34 | case 'CAD':
35 | afterpayCheckoutTermsLink = "https://www.afterpay.com/en-CA/instalment-agreement";
36 | break;
37 | case 'NZD':
38 | afterpayCheckoutTermsLink="https://www.afterpay.com/en-NZ/terms-of-service";
39 | break ;
40 | case 'AUD':
41 | afterpayCheckoutTermsLink="https://www.afterpay.com/en-AU/terms-of-service";
42 | break ;
43 | default:
44 | afterpayCheckoutTermsLink = "https://www.afterpay.com/terms/";
45 | }
46 | return afterpayCheckoutTermsLink;
47 | },
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/Model/Order/CreditMemo/CreditMemoInitiator.php:
--------------------------------------------------------------------------------
1 | creditmemoFactory = $creditmemoFactory;
13 | }
14 |
15 | public function init(\Magento\Sales\Model\Order $order): \Magento\Sales\Model\Order\Creditmemo
16 | {
17 | $qtysToRefund = [];
18 | /** @var \Magento\Sales\Model\Order\Item $orderItem */
19 | foreach ($order->getItemsCollection() as $orderItem) {
20 | if ($orderItem->getProductType() == \Magento\Bundle\Model\Product\Type::TYPE_CODE) {
21 | /** @var \Magento\Sales\Model\Order\Item $childrenItem */
22 | foreach ($orderItem->getChildrenItems() as $childrenItem) {
23 | if (!$childrenItem->getIsVirtual()) {
24 | $qtyShipped = $childrenItem->getQtyShipped();
25 | $qtyOrdered = $childrenItem->getQtyOrdered();
26 | $qtyRefunded = $childrenItem->getQtyRefunded();
27 | $childItemLeftToShip = $qtyOrdered - ($qtyShipped + $qtyRefunded);
28 | if ($childItemLeftToShip > 0) {
29 | $qtysToRefund[$childrenItem->getId()] = $childItemLeftToShip;
30 | }
31 | }
32 | }
33 | }
34 |
35 | if (!$orderItem->getParentItem() && !$orderItem->getIsVirtual()) {
36 | $qtyShipped = $orderItem->getQtyShipped();
37 | $qtyOrdered = $orderItem->getQtyOrdered();
38 | $qtyRefunded = $orderItem->getQtyRefunded();
39 | $itemLeftToShip = $qtyOrdered - ($qtyShipped + $qtyRefunded);
40 | if ($itemLeftToShip > 0) {
41 | $qtysToRefund[$orderItem->getId()] = $itemLeftToShip;
42 | }
43 | }
44 | }
45 | return $this->creditmemoFactory->createByOrder($order, ['qtys' => $qtysToRefund]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Gateway/Http/TransferFactory/UserAgentProvider.php:
--------------------------------------------------------------------------------
1 | moduleList = $moduleList;
21 | $this->productMetadata = $productMetadata;
22 | $this->util = $util;
23 | $this->config = $config;
24 | $this->store = $store;
25 | }
26 |
27 | public function provide(?int $websiteId = null): string
28 | {
29 | $afterpayModule = $this->moduleList->getOne('Afterpay_Afterpay');
30 | $moduleVersion = $afterpayModule['setup_version'] ?? null;
31 | $magentoProductName = $this->productMetadata->getName();
32 | $magentoProductEdition = $this->productMetadata->getEdition();
33 | $magentoVersion = $this->productMetadata->getVersion();
34 | $phpVersion = $this->util->getTrimmedPhpVersion();
35 | $afterpayMerchantId = $this->config->getMerchantId($websiteId);
36 | $publicId = $this->config->getPublicId($websiteId);
37 | $afterpayMPId=$publicId??"null";
38 | $websiteDomain = $this->store->getBaseUrl();
39 | $CashAppPayAvailable=(int)$this->config->getCashAppPayAvailable($websiteId);
40 | $CashAppPayEnabled=(int)$this->config->getCashAppPayEnabled($websiteId);
41 |
42 | return "AfterpayMagento2Plugin $moduleVersion ($magentoProductName $magentoProductEdition $magentoVersion) " .
43 | "PHPVersion: PHP/$phpVersion MerchantID: $afterpayMerchantId; MPID/$afterpayMPId; CAPAvailable/$CashAppPayAvailable; CAPEnabled/$CashAppPayEnabled; URL: $websiteDomain";
44 |
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Gateway/Response/DiscountHandler.php:
--------------------------------------------------------------------------------
1 | getPayment();
20 |
21 | $totalDiscount = $this->getOrderDiscountAmount($payment->getOrder());
22 | $paymentState = $payment->getAdditionalInformation(AdditionalInformationInterface::AFTERPAY_PAYMENT_STATE);
23 | if ($paymentState == PaymentStateInterface::CAPTURED) {
24 | $rolloverDiscount = '0.00';
25 | $capturedDiscount = $totalDiscount;
26 | } else {
27 | $rolloverDiscount = $totalDiscount;
28 | $capturedDiscount = '0.00';
29 | }
30 | $payment->setAdditionalInformation(
31 | AdditionalInformationInterface::AFTERPAY_ROLLOVER_DISCOUNT,
32 | $rolloverDiscount
33 | );
34 | $payment->setAdditionalInformation(
35 | AdditionalInformationInterface::AFTERPAY_CAPTURED_DISCOUNT,
36 | $capturedDiscount
37 | );
38 | }
39 |
40 | protected function getOrderDiscountAmount(\Magento\Sales\Model\Order $order): float
41 | {
42 | $isCBTCurrency = (bool) $order->getPayment()->getAdditionalInformation(
43 | \Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY
44 | );
45 |
46 | if ($isCBTCurrency) {
47 | return (float)($order->getGiftCardsAmount() + $order->getCustomerBalanceAmount() + $order->getRewardCurrencyAmount());
48 | } else {
49 | return (float)($order->getBaseGiftCardsAmount() + $order->getBaseCustomerBalanceAmount() + $order->getBaseRewardCurrencyAmount());
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Gateway/Response/MerchantConfiguration/ChannelsConfigurationHandler.php:
--------------------------------------------------------------------------------
1 | config = $config;
18 | $this->logger = $logger;
19 | }
20 |
21 | public function handle(array $handlingSubject, array $response): void
22 | {
23 | $websiteId = (int)$handlingSubject['websiteId'];
24 |
25 | if (isset($response['channels']) && is_array($response['channels'])) {
26 | $getCashAppConfig = array_search("CASH_APP", array_column($response['channels'], 'name'));
27 | $isCashAppEnabled = 0;
28 |
29 | if ( isset($response['channels'][$getCashAppConfig]) &&
30 | isset($response['channels'][$getCashAppConfig]['name']) &&
31 | strtoupper($response['channels'][$getCashAppConfig]['name'])== "CASH_APP") {
32 | $cashappData=$response['channels'][$getCashAppConfig];
33 |
34 | if (isset($cashappData['enabled']) && $cashappData['enabled']==true &&
35 | isset($cashappData['integrationCompleted']) && $cashappData['integrationCompleted']==true &&
36 | isset($cashappData['enabledForOrders']) && $cashappData['enabledForOrders']==true) {
37 | $isCashAppEnabled = (int)$cashappData['enabled'];
38 | }
39 |
40 | }else{
41 | if( $this->config->getCashAppPayActive($websiteId)==true) {
42 | //Disable the Cash App Pay if it's not available for the Merchant
43 | $this->config->setCashAppPayActive($isCashAppEnabled, $websiteId);
44 | $this->logger->notice("Disable the Cash App Pay as it's not available for the Merchant");
45 | }
46 | }
47 | $this->config->setCashAppPayAvailable($isCashAppEnabled, $websiteId);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Test/Unit/Service/Payment/Auth/ExpiryDateTest.php:
--------------------------------------------------------------------------------
1 | timezone = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class)
17 | ->disableOriginalConstructor()
18 | ->getMock();
19 | $this->expiryDate = new ExpiryDate($this->timezone);
20 | }
21 |
22 | /**
23 | * @dataProvider datesProvider
24 | */
25 | public function testIsExpired(string $expireDate, ?string $dateToCheck, bool $result)
26 | {
27 | if (!$dateToCheck) {
28 | $this->timezone->expects($this->once())->method("date")->willReturn(new \DateTime());
29 | }
30 | $this->assertEquals($this->expiryDate->isExpired($expireDate, $dateToCheck), $result);
31 | }
32 |
33 | public function datesProvider(): array
34 | {
35 | return [
36 | ["2021-08-10 09:31 CDT", "2021-08-10 09:31 CDT", false],
37 | ["2021-08-10 09:31 CDT", "2021-08-10 09:32 CDT", true],
38 | ["2021-08-10 09:31 CDT", "2024-11-11 09:32 CDT", true],
39 | ["2021-08-10 09:31 CDT", "2024-01-11 09:32 CDT", true],
40 | ["2021-07-05 09:31 CDT", "2020-01-11 09:32 CDT", false],
41 | [(new \DateTime())->format('Y-m-d H:i T'), null, false],
42 | [(new \DateTime())->modify('-1 minute')->format(ExpiryDate::FORMAT), null, true],
43 | [(new \DateTime())->modify('-1 days')->format(ExpiryDate::FORMAT), null, true],
44 | [(new \DateTime())->modify('-1 week')->format(ExpiryDate::FORMAT), null, true],
45 | [(new \DateTime())->modify('-1 year +2 days -3 minutes')->format(ExpiryDate::FORMAT), null, true],
46 | [(new \DateTime())->modify('+1 year +2 days -3 minutes')->format(ExpiryDate::FORMAT), null, false],
47 | [(new \DateTime())->modify('+1 day')->format(ExpiryDate::FORMAT), null, false],
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Model/Payment/AmountProcessor/VirtualProducts.php:
--------------------------------------------------------------------------------
1 | getAdditionalInformation(
10 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_CAPTURED_DISCOUNT
11 | ) ?? 0;
12 | $totalDiscountAmount = $payment->getAdditionalInformation(
13 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_ROLLOVER_DISCOUNT
14 | ) ?? 0;
15 |
16 | $isCBTCurrency = $payment->getAdditionalInformation(\Afterpay\Afterpay\Api\Data\CheckoutInterface::AFTERPAY_IS_CBT_CURRENCY);
17 | $orderTotal = $isCBTCurrency ? $payment->getOrder()->getGrandTotal() : $payment->getOrder()->getBaseGrandTotal();
18 | $totalWithoutVirtualProducts = $orderTotal - $amount;
19 | $returnAmount = $amount;
20 |
21 | if ($amount > $totalDiscountAmount) {
22 | $returnAmount -= $totalDiscountAmount;
23 | $capturedDiscount += $totalDiscountAmount;
24 | $totalDiscountAmount = '0.00';
25 | } else {
26 | if ($totalWithoutVirtualProducts < $totalDiscountAmount) {
27 | $returnAmount = $orderTotal;
28 | }
29 |
30 | $openToCapture = $payment->getAdditionalInformation(
31 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_OPEN_TO_CAPTURE_AMOUNT
32 | );
33 | if ($openToCapture && $openToCapture == $returnAmount) {
34 | $capturedDiscount += $totalDiscountAmount;
35 | $totalDiscountAmount = '0.00';
36 | }
37 | }
38 |
39 | $payment->setAdditionalInformation(
40 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_ROLLOVER_DISCOUNT,
41 | (string)$totalDiscountAmount
42 | );
43 | $payment->setAdditionalInformation(
44 | \Afterpay\Afterpay\Model\Payment\AdditionalInformationInterface::AFTERPAY_CAPTURED_DISCOUNT,
45 | $capturedDiscount
46 | );
47 |
48 | return $returnAmount;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Controller/Express/CreateCheckout.php:
--------------------------------------------------------------------------------
1 | checkoutManagement = $checkoutManagement;
25 | $this->checkoutSession = $checkoutSession;
26 | $this->url = $url;
27 | $this->jsonResultFactory = $jsonResultFactory;
28 | $this->messageManager = $messageManager;
29 | $this->logger = $logger;
30 | }
31 |
32 | public function execute(): \Magento\Framework\Controller\ResultInterface
33 | {
34 | $result = $this->jsonResultFactory->create();
35 | try {
36 | $checkout = $this->checkoutManagement->createExpress(
37 | (string)$this->checkoutSession->getQuoteId(),
38 | $this->url->getUrl('checkout/cart')
39 | );
40 | $result->setData([
41 | CheckoutInterface::AFTERPAY_TOKEN => $checkout->getAfterpayToken()
42 | ]);
43 | } catch (\Magento\Framework\Exception\LocalizedException $e) {
44 | $this->messageManager->addErrorMessage($e->getMessage());
45 | } catch (\Throwable $e) {
46 | $this->logger->error($e->getMessage());
47 | $this->messageManager->addErrorMessage((string)__('Afterpay payment is declined. Please select an alternative payment method.'));
48 | }
49 | return $result;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/etc/adminhtml/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | Afterpay\Afterpay\Gateway\Config\Config
7 |
8 |
9 |
10 |
11 | Afterpay\Afterpay\Model\Method\MethodFacade
12 |
13 |
14 |
15 |
16 | allowed_merchant_countries
17 |
18 |
19 |
20 |
21 | expresscheckout/allowed_merchant_countries
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/Form/Field/CBTAvailableCurrencies.php:
--------------------------------------------------------------------------------
1 | serializer = $serializer;
21 | $this->logger = $logger;
22 | parent::__construct($context, $data);
23 | }
24 |
25 | protected function _renderValue(\Magento\Framework\Data\Form\Element\AbstractElement $element)
26 | {
27 | try {
28 | if (!empty($element->getValue())) {
29 | $CbtAvailableCurrencies = $this->serializer->unserialize($element->getValue());
30 | $newValue = '';
31 | if (!$CbtAvailableCurrencies) {
32 | return parent::_renderValue($element);
33 | }
34 |
35 | foreach ($CbtAvailableCurrencies as $currencyCode => $currency) {
36 | $min = $currency['minimumAmount']['amount'] ?? '0';
37 | $newValue .= $currencyCode . '(min:' . $min . ',max:' . $currency['maximumAmount']['amount'] . ') ';
38 | }
39 | $element->setValue($newValue);
40 | }
41 | } catch (\Exception $e) {
42 | $this->logger->critical($e);
43 | }
44 |
45 | return parent::_renderValue($element);
46 | }
47 |
48 | public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element)
49 | {
50 | /** @phpstan-ignore-next-line */
51 | $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
52 | return parent::render($element);
53 | }
54 |
55 | protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
56 | {
57 | /** @phpstan-ignore-next-line */
58 | $element->setDisabled('disabled');
59 | return $element->getElementHtml();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------