diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 00000000..f4949b24 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,44 @@ +name: Release + +on: +# Manual run from Github UI (e.g. for when a merged PR labels have changed) + workflow_dispatch: + inputs: + pre-release: + required: false + type: boolean + default: false + description: "This release will be labeled as non-production ready" + # Publish the current version now, useful if the automated run failed + github-release: + required: false + type: boolean + default: false + description: "Publish Github release for the current version" + # Monitor pull request events + + pull_request: + types: + - closed + branches: + - main + +jobs: + release: + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Prepare the next main release + uses: Adyen/release-automation-action@v1.3.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + develop-branch: main + version-files: composer.json + release-title: Adyen Magento 2 Express Checkout Module + pre-release: ${{ inputs.pre-release || false }} +# For a manual Github release + github-release: ${{ inputs.github-release || false }} + separator: .pre.beta \ No newline at end of file diff --git a/Api/AdyenInitPaymentsInterface.php b/Api/AdyenInitPaymentsInterface.php new file mode 100644 index 00000000..a25137d4 --- /dev/null +++ b/Api/AdyenInitPaymentsInterface.php @@ -0,0 +1,31 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Api; + +interface AdyenInitPaymentsInterface +{ + const PAYMENT_CHANNEL_WEB = 'web'; + + /** + * @param string $stateData + * @param int|null $adyenCartId + * @param string|null $adyenMaskedQuoteId + * @return string + */ + public function execute( + string $stateData, + ?int $adyenCartId = null, + ?string $adyenMaskedQuoteId = null + ): string; +} diff --git a/Api/AdyenPaypalUpdateOrderInterface.php b/Api/AdyenPaypalUpdateOrderInterface.php new file mode 100755 index 00000000..c5a934a5 --- /dev/null +++ b/Api/AdyenPaypalUpdateOrderInterface.php @@ -0,0 +1,31 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Api; + +interface AdyenPaypalUpdateOrderInterface +{ + /** + * @param string $paymentData + * @param int|null $adyenCartId + * @param string|null $adyenMaskedQuoteId + * @param string $deliveryMethods + * @return mixed + */ + public function execute( + string $paymentData, + ?int $adyenCartId = null, + ?string $adyenMaskedQuoteId = null, + string $deliveryMethods = '' + ): string; +} diff --git a/Api/GuestAdyenInitPaymentsInterface.php b/Api/GuestAdyenInitPaymentsInterface.php new file mode 100644 index 00000000..b10d1b48 --- /dev/null +++ b/Api/GuestAdyenInitPaymentsInterface.php @@ -0,0 +1,29 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Api; + +interface GuestAdyenInitPaymentsInterface +{ + /** + * @param string $stateData + * @param string|null $guestMaskedId + * @param string|null $adyenMaskedQuoteId + * @return string + */ + public function execute( + string $stateData, + ?string $guestMaskedId = null, + ?string $adyenMaskedQuoteId = null + ): string; +} diff --git a/Api/GuestAdyenPaypalUpdateOrderInterface.php b/Api/GuestAdyenPaypalUpdateOrderInterface.php new file mode 100644 index 00000000..9e158aa7 --- /dev/null +++ b/Api/GuestAdyenPaypalUpdateOrderInterface.php @@ -0,0 +1,31 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Api; + +interface GuestAdyenPaypalUpdateOrderInterface +{ + /** + * @param string $paymentData + * @param string|null $guestMaskedId + * @param string|null $adyenMaskedQuoteId + * @param string $deliveryMethods + * @return mixed + */ + public function execute( + string $paymentData, + ?string $guestMaskedId = null, + ?string $adyenMaskedQuoteId = null, + string $deliveryMethods = '' + ): string; +} diff --git a/Block/ApplePay/Shortcut/Button.php b/Block/ApplePay/Shortcut/Button.php index ce779803..a00cada1 100644 --- a/Block/ApplePay/Shortcut/Button.php +++ b/Block/ApplePay/Shortcut/Button.php @@ -14,7 +14,6 @@ namespace Adyen\ExpressCheckout\Block\ApplePay\Shortcut; use Adyen\Payment\Helper\Data as AdyenHelper; -use Adyen\Payment\Helper\Config as AdyenConfigHelper; use Adyen\ExpressCheckout\Block\Buttons\AbstractButton; use Adyen\ExpressCheckout\Model\ConfigurationInterface; use Magento\Checkout\Model\Session; @@ -22,44 +21,19 @@ use Magento\Checkout\Model\DefaultConfigProvider; use Magento\Customer\Model\Session as CustomerSession; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\UrlInterface; use Magento\Framework\View\Element\Template\Context; use Magento\Payment\Model\MethodInterface; -use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; class Button extends AbstractButton implements ShortcutInterface { - /** - * @var DefaultConfigProvider $defaultConfigProvider - */ - private $defaultConfigProvider; - - /** - * @var UrlInterface $url - */ - private $url; - - /** - * @var CustomerSession $customerSession - */ - private $customerSession; - - /** - * @var StoreManagerInterface $storeManager - */ - private $storeManager; - - /** - * @var ScopeConfigInterface $scopeConfig - */ - private $scopeConfig; + const PAYMENT_METHOD_VARIANT = 'applepay'; /** * @var ConfigurationInterface $configuration */ - private $configuration; + private ConfigurationInterface $configuration; /** * Button Constructor @@ -73,7 +47,6 @@ class Button extends AbstractButton implements ShortcutInterface * @param DefaultConfigProvider $defaultConfigProvider * @param ScopeConfigInterface $scopeConfig * @param AdyenHelper $adyenHelper - * @param AdyenConfigHelper $adyenConfigHelper * @param ConfigurationInterface $configuration * @param array $data */ @@ -87,7 +60,6 @@ public function __construct( DefaultConfigProvider $defaultConfigProvider, ScopeConfigInterface $scopeConfig, AdyenHelper $adyenHelper, - AdyenConfigHelper $adyenConfigHelper, ConfigurationInterface $configuration, array $data = [] ) { @@ -100,40 +72,28 @@ public function __construct( $storeManagerInterface, $scopeConfig, $adyenHelper, - $adyenConfigHelper, + $defaultConfigProvider, $data ); - $this->defaultConfigProvider = $defaultConfigProvider; + $this->configuration = $configuration; } /** - * Current Quote ID for guests - * * @return string - * @throws LocalizedException - * @throws NoSuchEntityException */ - public function getQuoteId(): string + public function getButtonColor(): string { - try { - $config = $this->defaultConfigProvider->getConfig(); - if (!empty($config['quoteData']['entity_id'])) { - return $config['quoteData']['entity_id']; - } - } catch (NoSuchEntityException $e) { - if ($e->getMessage() !== 'No such entity with cartId = ') { - throw $e; - } - } - return ''; + return $this->configuration->getApplePayButtonColor(); } - /** - * @return string - */ - public function getButtonColor(): string + public function buildConfiguration(): array { - return $this->configuration->getApplePayButtonColor(); + $baseConfiguration = parent::buildConfiguration(); + $variant = $this->getPaymentMethodVariant(); + + $baseConfiguration["Adyen_ExpressCheckout/js/$variant/button"]['buttonColor'] = $this->getButtonColor(); + + return $baseConfiguration; } } diff --git a/Block/Buttons/AbstractButton.php b/Block/Buttons/AbstractButton.php index f8a12807..f720634d 100644 --- a/Block/Buttons/AbstractButton.php +++ b/Block/Buttons/AbstractButton.php @@ -13,18 +13,17 @@ namespace Adyen\ExpressCheckout\Block\Buttons; -use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\Data as AdyenHelper; +use Exception; +use Magento\Checkout\Model\DefaultConfigProvider; use Magento\Checkout\Model\Session; use Magento\Customer\Model\Session as CustomerSession; -use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\UrlInterface; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Element\Template\Context; use Magento\Payment\Model\MethodInterface; -use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\App\Config\ScopeConfigInterface; @@ -38,42 +37,42 @@ abstract class AbstractButton extends Template /** * @var Session */ - private $checkoutSession; + private Session $checkoutSession; /** * @var MethodInterface */ - private $payment; + private MethodInterface $payment; /** * @var UrlInterface $url */ - private $url; + private UrlInterface $url; /** * @var CustomerSession $customerSession */ - private $customerSession; + private CustomerSession $customerSession; /** * @var StoreManagerInterface $storeManager */ - private $storeManager; + private StoreManagerInterface $storeManager; /** * @var ScopeConfigInterface $scopeConfig */ - private $scopeConfig; + private ScopeConfigInterface $scopeConfig; /** * @var AdyenHelper */ - private $adyenHelper; + private AdyenHelper $adyenHelper; /** - * @var Config + * @var DefaultConfigProvider */ - private $adyenConfigHelper; + private DefaultConfigProvider $defaultConfigProvider; /** * Button constructor. @@ -85,7 +84,7 @@ abstract class AbstractButton extends Template * @param StoreManagerInterface $storeManagerInterface * @param ScopeConfigInterface $scopeConfig * @param AdyenHelper $adyenHelper - * @param Config $adyenConfigHelper + * @param DefaultConfigProvider $defaultConfigProvider * @param array $data */ public function __construct( @@ -97,7 +96,7 @@ public function __construct( StoreManagerInterface $storeManagerInterface, ScopeConfigInterface $scopeConfig, AdyenHelper $adyenHelper, - Config $adyenConfigHelper, + DefaultConfigProvider $defaultConfigProvider, array $data = [] ) { parent::__construct($context, $data); @@ -108,7 +107,7 @@ public function __construct( $this->storeManager = $storeManagerInterface; $this->scopeConfig = $scopeConfig; $this->adyenHelper = $adyenHelper; - $this->adyenConfigHelper = $adyenConfigHelper; + $this->defaultConfigProvider = $defaultConfigProvider; } /** @@ -255,4 +254,78 @@ public function getContainerId(): string { return $this->getData(self::BUTTON_ELEMENT_INDEX) ?: ''; } + + public function getRandomElementId(): string + { + try { + $id = sprintf('%s%s', $this->getContainerId(), random_int(PHP_INT_MIN, PHP_INT_MAX)); + } catch (Exception $e) { + /** + * Exception only thrown if an appropriate source of randomness cannot be found. + * https://www.php.net/manual/en/function.random-int.php + */ + $id = "0"; + } + + return $id; + } + + /** + * Current Quote ID for guests + * + * @return string + * @throws LocalizedException + * @throws NoSuchEntityException + */ + public function getQuoteId(): string + { + try { + $config = $this->defaultConfigProvider->getConfig(); + if (!empty($config['quoteData']['entity_id'])) { + return $config['quoteData']['entity_id']; + } + } catch (NoSuchEntityException $e) { + if ($e->getMessage() !== 'No such entity with cartId = ') { + throw $e; + } + } + return ''; + } + + /** + * Returns Adyen payment method variant + * + * @return string + */ + public function getPaymentMethodVariant(): string + { + return static::PAYMENT_METHOD_VARIANT; + } + + /** + * Returns the base configuration for express frontend + * + * @return array[] + * @throws LocalizedException + * @throws NoSuchEntityException + */ + public function buildConfiguration(): array + { + $variant = $this->getPaymentMethodVariant(); + + return [ + "Adyen_ExpressCheckout/js/$variant/button" => [ + 'actionSuccess' => $this->getActionSuccess(), + 'storeCode' => $this->getStorecode(), + 'countryCode' => $this->getDefaultCountryCode(), + 'currency' => $this->getCurrency(), + 'merchantAccount' => $this->getMerchantAccount(), + 'format' => $this->getFormat(), + 'locale' => $this->getLocale(), + 'originkey' => $this->getOriginKey(), + 'checkoutenv' => $this->getCheckoutEnvironment(), + 'isProductView' => (bool) $this->getIsProductView() + ] + ]; + } } diff --git a/Block/GooglePay/Shortcut/Button.php b/Block/GooglePay/Shortcut/Button.php index 0b0e4be5..e5900e27 100644 --- a/Block/GooglePay/Shortcut/Button.php +++ b/Block/GooglePay/Shortcut/Button.php @@ -13,111 +13,10 @@ namespace Adyen\ExpressCheckout\Block\GooglePay\Shortcut; -use Adyen\Payment\Helper\Data as AdyenHelper; -use Adyen\Payment\Helper\Config as AdyenConfigHelper; use Adyen\ExpressCheckout\Block\Buttons\AbstractButton; -use Magento\Checkout\Model\Session; use Magento\Catalog\Block\ShortcutInterface; -use Magento\Checkout\Model\DefaultConfigProvider; -use Magento\Customer\Model\Session as CustomerSession; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Exception\InputException; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\UrlInterface; -use Magento\Framework\View\Element\Template\Context; -use Magento\Payment\Model\MethodInterface; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Store\Model\StoreManagerInterface; class Button extends AbstractButton implements ShortcutInterface { - /** - * @var DefaultConfigProvider $defaultConfigProvider - */ - private $defaultConfigProvider; - - /** - * @var UrlInterface $url - */ - private $url; - - /** - * @var CustomerSession $customerSession - */ - private $customerSession; - - /** - * @var StoreManagerInterface $storeManager - */ - private $storeManager; - - /** - * @var ScopeConfigInterface $scopeConfig - */ - private $scopeConfig; - - /** - * Button constructor - * - * @param Context $context - * @param Session $checkoutSession - * @param MethodInterface $payment - * @param UrlInterface $url - * @param CustomerSession $customerSession - * @param StoreManagerInterface $storeManagerInterface - * @param DefaultConfigProvider $defaultConfigProvider - * @param ScopeConfigInterface $scopeConfig - * @param AdyenHelper $adyenHelper - * @param AdyenConfigHelper $adyenConfigHelper - * @param array $data - */ - public function __construct( - Context $context, - Session $checkoutSession, - MethodInterface $payment, - UrlInterface $url, - CustomerSession $customerSession, - StoreManagerInterface $storeManagerInterface, - DefaultConfigProvider $defaultConfigProvider, - ScopeConfigInterface $scopeConfig, - AdyenHelper $adyenHelper, - AdyenConfigHelper $adyenConfigHelper, - array $data = [] - ) { - parent::__construct( - $context, - $checkoutSession, - $payment, - $url, - $customerSession, - $storeManagerInterface, - $scopeConfig, - $adyenHelper, - $adyenConfigHelper, - $data - ); - $this->defaultConfigProvider = $defaultConfigProvider; - } - - /** - * Current Quote ID for guests - * - * @return string - * @throws LocalizedException - * @throws NoSuchEntityException - */ - public function getQuoteId(): string - { - try { - $config = $this->defaultConfigProvider->getConfig(); - if (!empty($config['quoteData']['entity_id'])) { - return $config['quoteData']['entity_id']; - } - } catch (NoSuchEntityException $e) { - if ($e->getMessage() !== 'No such entity with cartId = ') { - throw $e; - } - } - return ''; - } + const PAYMENT_METHOD_VARIANT = 'googlepay'; } diff --git a/Block/Paypal/Shortcut/Button.php b/Block/Paypal/Shortcut/Button.php new file mode 100644 index 00000000..e1e0add4 --- /dev/null +++ b/Block/Paypal/Shortcut/Button.php @@ -0,0 +1,22 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Block\Paypal\Shortcut; + +use Adyen\ExpressCheckout\Block\Buttons\AbstractButton; +use Magento\Catalog\Block\ShortcutInterface; + +class Button extends AbstractButton implements ShortcutInterface +{ + const PAYMENT_METHOD_VARIANT = 'paypal_express'; +} diff --git a/Helper/PaypalUpdateOrder.php b/Helper/PaypalUpdateOrder.php new file mode 100644 index 00000000..8e6ab060 --- /dev/null +++ b/Helper/PaypalUpdateOrder.php @@ -0,0 +1,118 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Helper; + +use Adyen\Model\Checkout\Amount; +use Adyen\Model\Checkout\DeliveryMethod; +use Adyen\Model\Checkout\PaypalUpdateOrderRequest; +use Adyen\Model\Checkout\PaypalUpdateOrderResponse; +use Adyen\Model\Checkout\TaxTotal; +use Adyen\Payment\Helper\Data; +use Adyen\Service\Checkout\UtilityApi; +use Adyen\AdyenException; +use Magento\Framework\Exception\NoSuchEntityException; + +class PaypalUpdateOrder +{ + /** + * @var Data + */ + private Data $adyenHelper; + + /** + * @param Data $adyenHelper + */ + public function __construct( + Data $adyenHelper + ) { + $this->adyenHelper = $adyenHelper; + } + + /** + * Creates and returns service class for Adyen Utility API + * + * @param $storeId + * @return UtilityApi + * @throws AdyenException + * @throws NoSuchEntityException + */ + public function createAdyenUtilityApiService($storeId): UtilityApi + { + return new UtilityApi($this->adyenHelper->initializeAdyenClient($storeId)); + } + + /** + * Builds the request object for /paypal/updateOrder endpoint of Adyen Checkout API + * + * @param string $pspReference + * @param string $paymentData + * @param int $amountValue + * @param string $amountCurrency + * @param array $deliveryMethods + * @return PaypalUpdateOrderRequest + */ + public function buildPaypalUpdateOrderRequest( + string $pspReference, + string $paymentData, + int $amountValue, + int $taxAmount, + string $amountCurrency, + array $deliveryMethods = [] + ): PaypalUpdateOrderRequest { + $amount = new Amount(); + $amount->setValue($amountValue); + $amount->setCurrency($amountCurrency); + + $taxTotalAmount = new Amount(); + $taxTotalAmount->setValue($taxAmount); + $taxTotalAmount->setCurrency($amountCurrency); + + $taxTotal = new TaxTotal(); + $taxTotal->setAmount($taxTotalAmount); + + $paypalUpdateOrderRequest = new PaypalUpdateOrderRequest(); + + $paypalUpdateOrderRequest->setPspReference($pspReference); + $paypalUpdateOrderRequest->setPaymentData($paymentData); + $paypalUpdateOrderRequest->setAmount($amount); + $paypalUpdateOrderRequest->setTaxTotal($taxTotal); + + if (!empty($deliveryMethods)) { + $deliveryMethodsObjectArray = []; + + foreach ($deliveryMethods as $deliveryMethod) { + $deliveryMethodObject = new DeliveryMethod($deliveryMethod); + $deliveryMethodsObjectArray[] = $deliveryMethodObject; + } + + $paypalUpdateOrderRequest->setDeliveryMethods($deliveryMethodsObjectArray); + } + + return $paypalUpdateOrderRequest; + } + + /** + * Returns paypal/updateOrder response in a structured array. + * + * @param PaypalUpdateOrderResponse $paypalUpdateOrderResponse + * @return array + */ + public function handlePaypalUpdateOrderResponse(PaypalUpdateOrderResponse $paypalUpdateOrderResponse): array + { + return [ + 'status' => $paypalUpdateOrderResponse->getStatus(), + 'paymentData' => $paypalUpdateOrderResponse->getPaymentData() + ]; + } +} diff --git a/Helper/Util/PaypalDeliveryMethodValidator.php b/Helper/Util/PaypalDeliveryMethodValidator.php new file mode 100644 index 00000000..18a9c800 --- /dev/null +++ b/Helper/Util/PaypalDeliveryMethodValidator.php @@ -0,0 +1,31 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Helper\Util; + +use Adyen\Payment\Helper\Util\DataArrayValidator; + +class PaypalDeliveryMethodValidator implements PaypalDeliveryMethodValidatorInterface +{ + public function getValidatedDeliveryMethod(array $deliveryMethod): array + { + if (!empty($deliveryMethod)) { + $deliveryMethod = DataArrayValidator::getArrayOnlyWithApprovedKeys( + $deliveryMethod, + self::DELIVERY_METHOD_FIELDS + ); + } + + return $deliveryMethod; + } +} diff --git a/Helper/Util/PaypalDeliveryMethodValidatorInterface.php b/Helper/Util/PaypalDeliveryMethodValidatorInterface.php new file mode 100644 index 00000000..69720cd5 --- /dev/null +++ b/Helper/Util/PaypalDeliveryMethodValidatorInterface.php @@ -0,0 +1,39 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Helper\Util; + +interface PaypalDeliveryMethodValidatorInterface +{ + const DELIVERY_METHOD_FIELD_REFERENCE = 'reference'; + const DELIVERY_METHOD_FIELD_DESCRIPTION = 'description'; + const DELIVERY_METHOD_FIELD_TYPE = 'type'; + const DELIVERY_METHOD_FIELD_AMOUNT = 'amount'; + const DELIVERY_METHOD_FIELD_SELECTED = 'selected'; + + const DELIVERY_METHOD_FIELDS = [ + self::DELIVERY_METHOD_FIELD_REFERENCE, + self::DELIVERY_METHOD_FIELD_DESCRIPTION, + self::DELIVERY_METHOD_FIELD_TYPE, + self::DELIVERY_METHOD_FIELD_AMOUNT, + self::DELIVERY_METHOD_FIELD_SELECTED + ]; + + /** + * Validates and clean-up the invalid data from PayPal's delivery methods and returns a valid array. + * + * @param array $deliveryMethod + * @return array + */ + public function getValidatedDeliveryMethod(array $deliveryMethod): array; +} diff --git a/Model/AdyenInitPayments.php b/Model/AdyenInitPayments.php new file mode 100644 index 00000000..08c18c34 --- /dev/null +++ b/Model/AdyenInitPayments.php @@ -0,0 +1,203 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Model; + +use Adyen\ExpressCheckout\Api\AdyenInitPaymentsInterface; +use Adyen\Payment\Gateway\Http\Client\TransactionPayment; +use Adyen\Payment\Gateway\Http\TransferFactory; +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\PaymentResponseHandler; +use Adyen\Payment\Helper\ReturnUrlHelper; +use Adyen\Payment\Helper\Util\CheckoutStateDataValidator; +use Exception; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\ValidatorException; +use Magento\Payment\Gateway\Http\ClientException; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; + +class AdyenInitPayments implements AdyenInitPaymentsInterface +{ + /** + * @var CartRepositoryInterface + */ + private CartRepositoryInterface $cartRepository; + + /** + * @var Config + */ + private Config $configHelper; + + /** + * @var ReturnUrlHelper + */ + private ReturnUrlHelper $returnUrlHelper; + + /** + * @var CheckoutStateDataValidator + */ + private CheckoutStateDataValidator $checkoutStateDataValidator; + + /** + * @var TransferFactory + */ + private TransferFactory $transferFactory; + + /** + * @var TransactionPayment + */ + private TransactionPayment $transactionPaymentClient; + + /** + * @var Data + */ + private Data $adyenHelper; + + /** + * @var PaymentResponseHandler + */ + private PaymentResponseHandler $paymentResponseHandler; + + /** + * @var QuoteIdMaskFactory + */ + private QuoteIdMaskFactory $quoteIdMaskFactory; + + /** + * @param CartRepositoryInterface $cartRepository + * @param Config $configHelper + * @param ReturnUrlHelper $returnUrlHelper + * @param CheckoutStateDataValidator $checkoutStateDataValidator + * @param TransferFactory $transferFactory + * @param TransactionPayment $transactionPaymentClient + * @param Data $adyenHelper + * @param PaymentResponseHandler $paymentResponseHandler + * @param QuoteIdMaskFactory $quoteIdMaskFactory + */ + public function __construct( + CartRepositoryInterface $cartRepository, + Config $configHelper, + ReturnUrlHelper $returnUrlHelper, + CheckoutStateDataValidator $checkoutStateDataValidator, + TransferFactory $transferFactory, + TransactionPayment $transactionPaymentClient, + Data $adyenHelper, + PaymentResponseHandler $paymentResponseHandler, + QuoteIdMaskFactory $quoteIdMaskFactory + ) { + $this->cartRepository = $cartRepository; + $this->configHelper = $configHelper; + $this->returnUrlHelper = $returnUrlHelper; + $this->checkoutStateDataValidator = $checkoutStateDataValidator; + $this->transferFactory = $transferFactory; + $this->transactionPaymentClient = $transactionPaymentClient; + $this->adyenHelper = $adyenHelper; + $this->paymentResponseHandler = $paymentResponseHandler; + $this->quoteIdMaskFactory = $quoteIdMaskFactory; + } + + /** + * @param string $stateData + * @param int|null $adyenCartId + * @param string|null $adyenMaskedQuoteId + * @return string + * @throws ClientException + * @throws NoSuchEntityException + * @throws ValidatorException + * @throws LocalizedException + */ + public function execute( + string $stateData, + ?int $adyenCartId = null, + ?string $adyenMaskedQuoteId = null + ): string { + if (is_null($adyenCartId)) { + /** @var $quoteIdMask QuoteIdMask */ + $quoteIdMask = $this->quoteIdMaskFactory->create()->load( + $adyenMaskedQuoteId, + 'masked_id' + ); + $adyenCartId = (int) $quoteIdMask->getQuoteId(); + } + + $quote = $this->cartRepository->get($adyenCartId); + + // Reserve an order ID for the quote to obtain the reference and save the quote + if (is_null($quote->getReservedOrderId())) { + $quote->reserveOrderId(); + $this->cartRepository->save($quote); + } + + $stateData = json_decode($stateData, true); + // Validate JSON that has just been parsed if it was in a valid format + if (json_last_error() !== JSON_ERROR_NONE) { + throw new ValidatorException( + __('Payments call failed because stateData was not a valid JSON!') + ); + } + // Validate the keys in stateData and remove invalid keys + $stateData = $this->checkoutStateDataValidator->getValidatedAdditionalData($stateData); + $paymentsRequest = $this->buildPaymentsRequest($quote, $stateData); + + $transfer = $this->transferFactory->create([ + 'body' => $paymentsRequest, + 'clientConfig' => ['storeId' => $quote->getStoreId()] + ]); + + try { + $response = $this->transactionPaymentClient->placeRequest($transfer); + return json_encode( + $this->paymentResponseHandler->formatPaymentResponse($response['resultCode'], $response['action']) + ); + } catch (Exception $e) { + throw new ClientException( + __('Error with payment method, please select a different payment method!') + ); + } + } + + /** + * @param Quote $quote + * @param array $stateData + * @return array + */ + protected function buildPaymentsRequest(Quote $quote, array $stateData): array + { + $merchantReference = $quote->getReservedOrderId(); + $storeId = $quote->getStoreId(); + $returnUrl = sprintf( + "%s?merchantReference=%s", + $this->returnUrlHelper->getStoreReturnUrl($storeId), + $merchantReference + ); + $currency = $quote->getQuoteCurrencyCode(); + $amount = $quote->getSubtotalWithDiscount(); + $request = [ + 'amount' => [ + 'currency' => $currency, + 'value' => $this->adyenHelper->formatAmount($amount, $currency) + ], + 'reference' => $merchantReference, + 'returnUrl' => $returnUrl, + 'merchantAccount' => $this->configHelper->getMerchantAccount($storeId), + 'channel' => self::PAYMENT_CHANNEL_WEB + ]; + + return array_merge($request, $stateData); + } +} diff --git a/Model/AdyenPaypalUpdateOrder.php b/Model/AdyenPaypalUpdateOrder.php new file mode 100755 index 00000000..b176d38c --- /dev/null +++ b/Model/AdyenPaypalUpdateOrder.php @@ -0,0 +1,217 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Model; + +use Adyen\AdyenException; +use Adyen\Client; +use Adyen\ExpressCheckout\Api\AdyenPaypalUpdateOrderInterface; +use Adyen\ExpressCheckout\Helper\PaypalUpdateOrder; +use Adyen\ExpressCheckout\Helper\Util\PaypalDeliveryMethodValidator; +use Adyen\ExpressCheckout\Model\ResourceModel\PaymentResponse\Collection as AdyenPaymentResponseCollection; +use Adyen\Payment\Helper\ChargedCurrency; +use Adyen\Payment\Helper\Data; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\ValidatorException; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; + +class AdyenPaypalUpdateOrder implements AdyenPaypalUpdateOrderInterface +{ + /** + * @var PaypalUpdateOrder + */ + protected PaypalUpdateOrder $paypalUpdateOrderHelper; + + /** + * @var CartRepositoryInterface + */ + private CartRepositoryInterface $cartRepository; + + /** + * @var PaypalDeliveryMethodValidator + */ + private PaypalDeliveryMethodValidator $deliveryMethodValidator; + + /** + * @var ChargedCurrency + */ + private ChargedCurrency $chargedCurrency; + + /** + * @var Data + */ + private Data $adyenHelper; + + /** + * @var AdyenPaymentResponseCollection + */ + private AdyenPaymentResponseCollection $paymentResponseCollection; + + /** + * @var QuoteIdMaskFactory + */ + private QuoteIdMaskFactory $quoteIdMaskFactory; + + /** + * @param PaypalUpdateOrder $updatePaypalOrderHelper + * @param CartRepositoryInterface $cartRepository + * @param PaypalDeliveryMethodValidator $deliveryMethodValidator + * @param ChargedCurrency $chargedCurrency + * @param Data $adyenHelper + * @param AdyenPaymentResponseCollection $paymentResponseCollection + * @param QuoteIdMaskFactory $quoteIdMaskFactory + */ + public function __construct( + PaypalUpdateOrder $updatePaypalOrderHelper, + CartRepositoryInterface $cartRepository, + PaypalDeliveryMethodValidator $deliveryMethodValidator, + ChargedCurrency $chargedCurrency, + Data $adyenHelper, + AdyenPaymentResponseCollection $paymentResponseCollection, + QuoteIdMaskFactory $quoteIdMaskFactory + ) { + $this->paypalUpdateOrderHelper = $updatePaypalOrderHelper; + $this->cartRepository = $cartRepository; + $this->deliveryMethodValidator = $deliveryMethodValidator; + $this->chargedCurrency = $chargedCurrency; + $this->adyenHelper = $adyenHelper; + $this->paymentResponseCollection = $paymentResponseCollection; + $this->quoteIdMaskFactory = $quoteIdMaskFactory; + } + + /** + * @param string $paymentData + * @param int|null $adyenCartId + * @param string|null $adyenMaskedQuoteId + * @param string $deliveryMethods + * @return string + * @throws NoSuchEntityException + * @throws ValidatorException + */ + public function execute( + string $paymentData, + ?int $adyenCartId = null, + ?string $adyenMaskedQuoteId = null, + string $deliveryMethods = '' + ): string { + if (is_null($adyenCartId)) { + /** @var $quoteIdMask QuoteIdMask */ + $quoteIdMask = $this->quoteIdMaskFactory->create()->load( + $adyenMaskedQuoteId, + 'masked_id' + ); + $adyenCartId = (int) $quoteIdMask->getQuoteId(); + } + + /** @var Quote $quote */ + $quote = $this->cartRepository->get($adyenCartId); + $merchantReference = $quote->getReservedOrderId(); + $deliveryMethods = json_decode($deliveryMethods, true); + + foreach ($deliveryMethods as &$method) { + // Ensure the amount value is an integer + $method['amount']['value'] = (int) $method['amount']['value']; + + // Validate the current method + $validatedMethod = $this->deliveryMethodValidator->getValidatedDeliveryMethod([$method]); + + // Replace the original method with the validated one + if (!empty($validatedMethod)) { + $method = $validatedMethod[0]; + } + } + unset($method); + + // Handle the case where JSON decoding fails + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \InvalidArgumentException('Invalid JSON provided for delivery methods.'); + } + if (is_null($merchantReference)) { + throw new ValidatorException( + __('Order ID has not been reserved!') + ); + } + + $paymentResponse = $this->paymentResponseCollection->getPaymentResponseWithMerchantReference( + $merchantReference + ); + + if (is_null($paymentResponse)) { + throw new ValidatorException( + __('Payment response couldn\'t be found!') + ); + } + + $decodedPaymentResponse = json_decode($paymentResponse['response'], true); + if (!isset($decodedPaymentResponse['pspReference'])) { + throw new ValidatorException( + __('Payment pspreference does not exist in the payment response!') + ); + } else { + $pspReference = $decodedPaymentResponse['pspReference']; + } + + $storeId = $quote->getStoreId(); + $quoteAmountCurrency = $this->chargedCurrency->getQuoteAmountCurrency($quote); + $amountCurrency = $quoteAmountCurrency->getCurrencyCode(); + $amountValue = $this->adyenHelper->formatAmount($quote->getGrandTotal(), $amountCurrency); + + if ($quote->isVirtual()) { + $taxAmount = $quote->getBillingAddress()->getTaxAmount(); + } else { + $taxAmount = $quote->getShippingAddress()->getTaxAmount(); + } + + $formattedTaxAmount = $this->adyenHelper->formatAmount($taxAmount, $amountCurrency); + + try { + $paypalUpdateOrderService = $this->paypalUpdateOrderHelper->createAdyenUtilityApiService($storeId); + $paypalUpdateOrderRequest = $this->paypalUpdateOrderHelper->buildPaypalUpdateOrderRequest( + $pspReference, + $paymentData, + $amountValue, + $formattedTaxAmount, + $amountCurrency, + $deliveryMethods + ); + + $this->adyenHelper->logRequest( + $paypalUpdateOrderRequest->toArray(), + Client::API_CHECKOUT_VERSION, + '/paypal/updateOrder' + ); + + $paypalUpdateOrderResponse = $paypalUpdateOrderService->updatesOrderForPaypalExpressCheckout( + $paypalUpdateOrderRequest + ); + } catch (AdyenException $e) { + $errorResponse['error'] = $e->getMessage(); + $errorResponse['errorCode'] = $e->getAdyenErrorCode(); + + $this->adyenHelper->logResponse($errorResponse); + + throw new ValidatorException( + __('Error with payment method, please select a different payment method.') + ); + } + + $this->adyenHelper->logResponse($paypalUpdateOrderResponse->toArray()); + + return json_encode( + $this->paypalUpdateOrderHelper->handlePaypalUpdateOrderResponse($paypalUpdateOrderResponse) + ); + } +} diff --git a/Model/Configuration.php b/Model/Configuration.php index 5a4695f5..383f2ea6 100644 --- a/Model/Configuration.php +++ b/Model/Configuration.php @@ -36,26 +36,27 @@ public function __construct( } /** - * Returns configuration value for where to show apple pay - * + * @param string $paymentMethodVariant * @param string $scopeType - * @param null|int|string $scopeCode + * @param $scopeCode * @return array */ - public function getShowApplePayOn( + public function getShowPaymentMethodOn( + string $paymentMethodVariant, string $scopeType = ScopeInterface::SCOPE_STORE, $scopeCode = null ): array { - $value = $this->scopeConfig->getValue( - self::SHOW_APPLE_PAY_ON_CONFIG_PATH, - $scopeType, - $scopeCode + $configPath = sprintf( + "%s/%s_%s/%s", + self::CONFIG_PATH_PAYMENT, + self::CONFIG_PATH_ADYEN_PREFIX, + $paymentMethodVariant, + self::CONFIG_PATH_SHOW_EXPRESS_ON ); - return $value ? - explode( - ',', - $value - ) : []; + + $value = $this->scopeConfig->getValue($configPath, $scopeType, $scopeCode); + + return $value ? explode(',', $value) : []; } /** @@ -63,7 +64,7 @@ public function getShowApplePayOn( */ public function getApplePayButtonColor( string $scopeType = ScopeInterface::SCOPE_STORE, - $scopeCode = null + $scopeCode = null ): string { $value = $this->scopeConfig->getValue( self::APPLE_PAY_BUTTON_COLOR_CONFIG_PATH, @@ -75,6 +76,33 @@ public function getApplePayButtonColor( } /** + * @deprecated use getShowPaymentMethodOn() instead + * + * Returns configuration value for where to show apple pay + * + * @param string $scopeType + * @param null|int|string $scopeCode + * @return array + */ + public function getShowApplePayOn( + string $scopeType = ScopeInterface::SCOPE_STORE, + $scopeCode = null + ): array { + $value = $this->scopeConfig->getValue( + self::SHOW_APPLE_PAY_ON_CONFIG_PATH, + $scopeType, + $scopeCode + ); + return $value ? + explode( + ',', + $value + ) : []; + } + + /** + * @deprecated use getShowPaymentMethodOn() instead + * * Returns configuration value for where to show google pay * * @param string $scopeType diff --git a/Model/ConfigurationInterface.php b/Model/ConfigurationInterface.php index 6539ed97..c2a4cdb7 100644 --- a/Model/ConfigurationInterface.php +++ b/Model/ConfigurationInterface.php @@ -22,20 +22,28 @@ */ interface ConfigurationInterface { + public const CONFIG_PATH_ADYEN_PREFIX = 'adyen'; + public const CONFIG_PATH_PAYMENT = 'payment'; + public const CONFIG_PATH_SHOW_EXPRESS_ON = 'express_show_on'; + public const CONFIG_PATH_EXPRESS_BUTTON_COLOR = 'express_button_color'; + + /** @deprecated */ + public const APPLE_PAY_BUTTON_COLOR_CONFIG_PATH = 'payment/adyen_express/apple_pay_button_color'; + /** @deprecated */ public const SHOW_APPLE_PAY_ON_CONFIG_PATH = 'payment/adyen_express/show_apple_pay_on'; + /** @deprecated */ public const SHOW_GOOGLE_PAY_ON_CONFIG_PATH = 'payment/adyen_express/show_google_pay_on'; - public const APPLE_PAY_BUTTON_COLOR_CONFIG_PATH = 'payment/adyen_express/apple_pay_button_color'; /** - * Returns configuration value for where to show apple pay - * + * @param string $paymentMethodVariant * @param string $scopeType - * @param null|int|string $scopeCode + * @param null $scopeCode * @return array */ - public function getShowApplePayOn( + public function getShowPaymentMethodOn( + string $paymentMethodVariant, string $scopeType = ScopeInterface::SCOPE_STORE, - $scopeCode = null + $scopeCode = null ): array; /** @@ -47,10 +55,26 @@ public function getShowApplePayOn( */ public function getApplePayButtonColor( string $scopeType = ScopeInterface::SCOPE_STORE, - $scopeCode = null + $scopeCode = null ): string; /** + * @deprecated use getShowPaymentMethodOn() instead + * + * Returns configuration value for where to show apple pay + * + * @param string $scopeType + * @param null|int|string $scopeCode + * @return array + */ + public function getShowApplePayOn( + string $scopeType = ScopeInterface::SCOPE_STORE, + $scopeCode = null + ): array; + + /** + * @deprecated use getShowPaymentMethodOn() instead + * * Returns configuration value for where to show google pay * * @param string $scopeType diff --git a/Model/GetAdyenPaymentMethodsByProduct.php b/Model/GetAdyenPaymentMethodsByProduct.php index fe975bb1..307928b4 100644 --- a/Model/GetAdyenPaymentMethodsByProduct.php +++ b/Model/GetAdyenPaymentMethodsByProduct.php @@ -104,7 +104,7 @@ public function execute( "countryCode" => $this->getCurrentCountryCode($store), "shopperLocale" => $this->adyenHelper->getCurrentLocaleCode($store->getId()), "amount" => [ - "value" => (float) $adyenAmountCurrency->getAmount(), + "value" => $this->adyenHelper->formatAmount($adyenAmountCurrency->getAmount(), $currencyCode), "currency" => $currencyCode ] ]; diff --git a/Model/GuestAdyenInitPayments.php b/Model/GuestAdyenInitPayments.php new file mode 100644 index 00000000..d6717147 --- /dev/null +++ b/Model/GuestAdyenInitPayments.php @@ -0,0 +1,75 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Model; + +use Adyen\ExpressCheckout\Api\GuestAdyenInitPaymentsInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\ValidatorException; +use Magento\Payment\Gateway\Http\ClientException; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; + +class GuestAdyenInitPayments implements GuestAdyenInitPaymentsInterface +{ + /** + * @var QuoteIdMaskFactory + */ + private QuoteIdMaskFactory $quoteIdMaskFactory; + + /** + * @var AdyenInitPayments + */ + private AdyenInitPayments $adyenInitPayments; + + /** + * @param QuoteIdMaskFactory $quoteIdMaskFactory + * @param AdyenInitPayments $adyenInitPayments + */ + public function __construct( + QuoteIdMaskFactory $quoteIdMaskFactory, + AdyenInitPayments $adyenInitPayments + ) { + $this->quoteIdMaskFactory = $quoteIdMaskFactory; + $this->adyenInitPayments = $adyenInitPayments; + } + + /** + * @param string $stateData + * @param string|null $guestMaskedId + * @param string|null $adyenMaskedQuoteId + * @return string + * @throws ClientException + * @throws NoSuchEntityException + * @throws ValidatorException + * @throws LocalizedException + */ + public function execute( + string $stateData, + ?string $guestMaskedId = null, + ?string $adyenMaskedQuoteId = null + ): string { + $quoteId = null; + if ($guestMaskedId !== null) { + /** @var $quoteIdMask QuoteIdMask */ + $quoteIdMask = $this->quoteIdMaskFactory->create()->load( + $guestMaskedId, + 'masked_id' + ); + $quoteId = (int) $quoteIdMask->getQuoteId(); + } + + return $this->adyenInitPayments->execute($stateData, $quoteId, $adyenMaskedQuoteId); + } +} diff --git a/Model/GuestAdyenPaypalUpdateOrder.php b/Model/GuestAdyenPaypalUpdateOrder.php new file mode 100644 index 00000000..ef0a4763 --- /dev/null +++ b/Model/GuestAdyenPaypalUpdateOrder.php @@ -0,0 +1,63 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Model; + +use Adyen\ExpressCheckout\Api\AdyenPaypalUpdateOrderInterface; +use Adyen\ExpressCheckout\Api\GuestAdyenPaypalUpdateOrderInterface; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; + +class GuestAdyenPaypalUpdateOrder implements GuestAdyenPaypalUpdateOrderInterface +{ + /** + * @var QuoteIdMaskFactory + */ + private QuoteIdMaskFactory $quoteIdMaskFactory; + + /** + * @var AdyenPaypalUpdateOrder + */ + private AdyenPaypalUpdateOrder $adyenUpdatePaypalOrder; + + /** + * @param QuoteIdMaskFactory $quoteIdMaskFactory + * @param AdyenPaypalUpdateOrderInterface $adyenUpdatePaypalOrder + */ + public function __construct( + QuoteIdMaskFactory $quoteIdMaskFactory, + AdyenPaypalUpdateOrderInterface $adyenUpdatePaypalOrder + ) { + $this->quoteIdMaskFactory = $quoteIdMaskFactory; + $this->adyenUpdatePaypalOrder = $adyenUpdatePaypalOrder; + } + + public function execute( + string $paymentData, + ?string $guestMaskedId = null, + ?string $adyenMaskedQuoteId = null, + string $deliveryMethods = '' + ): string { + $quoteId = null; + if ($guestMaskedId !== null) { + /** @var $quoteIdMask QuoteIdMask */ + $quoteIdMask = $this->quoteIdMaskFactory->create()->load( + $guestMaskedId, + 'masked_id' + ); + $quoteId = (int) $quoteIdMask->getQuoteId(); + } + + return $this->adyenUpdatePaypalOrder->execute($paymentData, $quoteId, $adyenMaskedQuoteId, $deliveryMethods); + } +} diff --git a/Model/ResourceModel/PaymentResponse/Collection.php b/Model/ResourceModel/PaymentResponse/Collection.php new file mode 100644 index 00000000..289cc0a5 --- /dev/null +++ b/Model/ResourceModel/PaymentResponse/Collection.php @@ -0,0 +1,30 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Model\ResourceModel\PaymentResponse; + +use Adyen\Payment\Model\ResourceModel\PaymentResponse\Collection as AdyenPaymentResponseCollection; + +class Collection extends AdyenPaymentResponseCollection +{ + /** + * Fetch the payment response for the merchant reference supplied + * + * @param string $merchantReference + * @return array|null + */ + public function getPaymentResponseWithMerchantReference(string $merchantReference): ?array + { + return $this->addFieldToFilter('merchant_reference', $merchantReference)->getLastItem()->getData(); + } +} diff --git a/Observer/AbstractPaymentMethodShortcuts.php b/Observer/AbstractPaymentMethodShortcuts.php new file mode 100644 index 00000000..36b9b914 --- /dev/null +++ b/Observer/AbstractPaymentMethodShortcuts.php @@ -0,0 +1,111 @@ + + */ +namespace Adyen\ExpressCheckout\Observer; + +use Adyen\ExpressCheckout\Model\Config\Source\ShortcutAreas; +use Adyen\ExpressCheckout\Model\ConfigurationInterface; +use Magento\Catalog\Block\ShortcutButtons; +use Magento\Catalog\Block\ShortcutInterface; +use Magento\Checkout\Block\QuoteShortcutButtons; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Exception\LocalizedException; + +abstract class AbstractPaymentMethodShortcuts implements ObserverInterface +{ + /** + * @var ConfigurationInterface + */ + private ConfigurationInterface $configuration; + + /** + * @var ShortcutInterface + */ + private ShortcutInterface $shortcutButton; + + /** + * @param ConfigurationInterface $configuration + * @param ShortcutInterface $shortcutButton + */ + public function __construct( + ConfigurationInterface $configuration, + ShortcutInterface $shortcutButton + ) { + $this->configuration = $configuration; + $this->shortcutButton = $shortcutButton; + } + + /** + * @param Observer $observer + * @return void + * @throws LocalizedException + */ + public function execute(Observer $observer): void + { + $currentPageIdentifier = $this->getCurrentPageIdentifier($observer); + $showPaymentMethodOn = $this->configuration->getShowPaymentMethodOn( + $this->shortcutButton->getPaymentMethodVariant() + ); + if (!in_array( + $currentPageIdentifier, + $showPaymentMethodOn + )) { + return; + } + /** @var ShortcutButtons $shortcutButtons */ + $shortcutButtons = $observer->getEvent()->getContainer(); + $shortcut = $shortcutButtons->getLayout()->createBlock($this->shortcutButton::class); + $isProductView = false; + $isCart = false; + $handles = $shortcutButtons->getLayout()->getUpdate()->getHandles(); + + // Check if any of the layout handles indicate a product page or cart page + if (in_array('catalog_product_view', $handles)) { + $isProductView = true; + } + elseif(in_array('checkout_cart_index', $handles)) { + $isCart = true; + } + $shortcut->setIsProductView($isProductView); + $shortcut->setIsCart($isCart); + $shortcutButtons->addShortcut($shortcut); + } + + /** + * Return current page identifier to compare with config values + * + * @param Observer $observer + * @return int + */ + private function getCurrentPageIdentifier( + Observer $observer + ): int { + $shortcutsBlock = $observer->getEvent()->getContainer(); + $handles = $shortcutsBlock->getLayout()->getUpdate()->getHandles(); + + //Check MiniCart + if ((bool)$shortcutsBlock->getData('is_minicart') === true) { + return ShortcutAreas::MINICART_VALUE; + } + + //Check Cart Page or PDP + if(in_array('catalog_product_view', $handles)) { + return ShortcutAreas::PRODUCT_VIEW_VALUE; + } + elseif(in_array('checkout_cart_index', $handles)) { + return ShortcutAreas::CART_PAGE_VALUE; + } + else { + return 0; + } + } +} diff --git a/Observer/AddApplePayShortcuts.php b/Observer/AddApplePayShortcuts.php index b39f55a8..921cad93 100644 --- a/Observer/AddApplePayShortcuts.php +++ b/Observer/AddApplePayShortcuts.php @@ -12,73 +12,15 @@ namespace Adyen\ExpressCheckout\Observer; use Adyen\ExpressCheckout\Block\ApplePay\Shortcut\Button; -use Adyen\ExpressCheckout\Model\Config\Source\ShortcutAreas; use Adyen\ExpressCheckout\Model\ConfigurationInterface; -use Magento\Catalog\Block\ShortcutButtons; -use Magento\Checkout\Block\QuoteShortcutButtons; -use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; -use Magento\Framework\Exception\LocalizedException; -class AddApplePayShortcuts implements ObserverInterface +class AddApplePayShortcuts extends AbstractPaymentMethodShortcuts implements ObserverInterface { - /** - * @var ConfigurationInterface - */ - private $configuration; - - /** - * AddApplePayShortcuts constructor - * - * @param ConfigurationInterface $configuration - */ public function __construct( - ConfigurationInterface $configuration + ConfigurationInterface $configuration, + Button $applepayButton ) { - $this->configuration = $configuration; - } - - /** - * Add apple pay shortcut button - * - * @param Observer $observer - * @return void - * @throws LocalizedException - */ - public function execute(Observer $observer) - { - $currentPageIdentifier = $this->getCurrentPageIdentifier($observer); - if (!in_array( - $currentPageIdentifier, - $this->configuration->getShowApplePayOn() - )) { - return; - } - /** @var ShortcutButtons $shortcutButtons */ - $shortcutButtons = $observer->getEvent()->getContainer(); - $shortcut = $shortcutButtons->getLayout()->createBlock(Button::class); - $shortcut->setIsProductView((bool)$observer->getData('is_catalog_product')); - $shortcut->setIsCart(get_class($shortcutButtons) === QuoteShortcutButtons::class); - $shortcutButtons->addShortcut($shortcut); - } - - /** - * Return current page identifier to compare with config values - * - * @param Observer $observer - * @return int - */ - private function getCurrentPageIdentifier( - Observer $observer - ): int { - if ($observer->getData('is_catalog_product')) { - return ShortcutAreas::PRODUCT_VIEW_VALUE; - } - $shortcutsBlock = $observer->getEvent()->getContainer(); - $isMinicart = (bool) $shortcutsBlock->getData('is_minicart'); - if ($isMinicart === true) { - return ShortcutAreas::MINICART_VALUE; - } - return ShortcutAreas::CART_PAGE_VALUE; + parent::__construct($configuration, $applepayButton); } } diff --git a/Observer/AddGooglePayShortcuts.php b/Observer/AddGooglePayShortcuts.php index c7d72b7f..33571bca 100644 --- a/Observer/AddGooglePayShortcuts.php +++ b/Observer/AddGooglePayShortcuts.php @@ -12,73 +12,15 @@ namespace Adyen\ExpressCheckout\Observer; use Adyen\ExpressCheckout\Block\GooglePay\Shortcut\Button; -use Adyen\ExpressCheckout\Model\Config\Source\ShortcutAreas; use Adyen\ExpressCheckout\Model\ConfigurationInterface; -use Magento\Catalog\Block\ShortcutButtons; -use Magento\Checkout\Block\QuoteShortcutButtons; -use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; -use Magento\Framework\Exception\LocalizedException; -class AddGooglePayShortcuts implements ObserverInterface +class AddGooglePayShortcuts extends AbstractPaymentMethodShortcuts implements ObserverInterface { - /** - * @var ConfigurationInterface - */ - private $configuration; - - /** - * AddGooglePayShortcuts constructor - * - * @param ConfigurationInterface $configuration - */ public function __construct( - ConfigurationInterface $configuration + ConfigurationInterface $configuration, + Button $googlepayButton ) { - $this->configuration = $configuration; - } - - /** - * Add google pay shortcut button - * - * @param Observer $observer - * @return void - * @throws LocalizedException - */ - public function execute(Observer $observer) - { - $currentPageIdentifier = $this->getCurrentPageIdentifier($observer); - if (!in_array( - $currentPageIdentifier, - $this->configuration->getShowGooglePayOn() - )) { - return; - } - /** @var ShortcutButtons $shortcutButtons */ - $shortcutButtons = $observer->getEvent()->getContainer(); - $shortcut = $shortcutButtons->getLayout()->createBlock(Button::class); - $shortcut->setIsProductView((bool)$observer->getData('is_catalog_product')); - $shortcut->setIsCart(get_class($shortcutButtons) === QuoteShortcutButtons::class); - $shortcutButtons->addShortcut($shortcut); - } - - /** - * Return current page identifier to compare with config values - * - * @param Observer $observer - * @return int - */ - private function getCurrentPageIdentifier( - Observer $observer - ): int { - if ($observer->getData('is_catalog_product')) { - return ShortcutAreas::PRODUCT_VIEW_VALUE; - } - $shortcutsBlock = $observer->getEvent()->getContainer(); - $isMinicart = (bool) $shortcutsBlock->getData('is_minicart'); - if ($isMinicart === true) { - return ShortcutAreas::MINICART_VALUE; - } - return ShortcutAreas::CART_PAGE_VALUE; + parent::__construct($configuration, $googlepayButton); } } diff --git a/Observer/AddPaypalShortcuts.php b/Observer/AddPaypalShortcuts.php new file mode 100644 index 00000000..84eb8cfe --- /dev/null +++ b/Observer/AddPaypalShortcuts.php @@ -0,0 +1,26 @@ + + */ +namespace Adyen\ExpressCheckout\Observer; + +use Adyen\ExpressCheckout\Block\Paypal\Shortcut\Button; +use Adyen\ExpressCheckout\Model\ConfigurationInterface; +use Magento\Framework\Event\ObserverInterface; + +class AddPaypalShortcuts extends AbstractPaymentMethodShortcuts implements ObserverInterface +{ + public function __construct( + ConfigurationInterface $configuration, + Button $paypalButton + ) { + parent::__construct($configuration, $paypalButton); + } +} diff --git a/Observer/SubmitQuoteObserver.php b/Observer/SubmitQuoteObserver.php new file mode 100644 index 00000000..d1880067 --- /dev/null +++ b/Observer/SubmitQuoteObserver.php @@ -0,0 +1,42 @@ + + */ + +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Observer; + +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Event\Observer; +use Magento\Sales\Model\Order; + +class SubmitQuoteObserver implements ObserverInterface +{ + /** + * Execute method for the observer. + * + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer) + { + $order = $observer->getEvent()->getOrder(); + $payment = $order->getPayment(); + + if ($payment->getMethod() == 'adyen_paypal_express') { + $payment->setMethod('adyen_paypal'); + } + + $order->setState(Order::STATE_NEW); + $order->setStatus($order->getConfig()->getStateDefaultStatus(Order:: STATE_NEW)); + $order->save(); + } +} diff --git a/Plugin/Gateway/Request/CheckoutDataBuilder.php b/Plugin/Gateway/Request/CheckoutDataBuilder.php index a5006b0a..1876d5f4 100644 --- a/Plugin/Gateway/Request/CheckoutDataBuilder.php +++ b/Plugin/Gateway/Request/CheckoutDataBuilder.php @@ -51,8 +51,8 @@ public function afterBuild( /** @var PaymentDataObject $paymentDataObject */ $paymentDataObject = SubjectReader::readPayment($buildSubject); $payment = $paymentDataObject->getPayment(); - $isAppleOrGooglePay = $this->isExpressMethodResolver->execute($payment); - if ($isAppleOrGooglePay === true) { + $isExpressMethod = $this->isExpressMethodResolver->execute($payment); + if ($isExpressMethod === true) { $paymentMethodStateData = $paymentAdditionalInfo['stateData']['paymentMethod'] ?? []; $result['body']['paymentMethod'] = $paymentMethodStateData; } diff --git a/Setup/Patch/Abstract/AbstractConfigurationPathPatcher.php b/Setup/Patch/Abstract/AbstractConfigurationPathPatcher.php new file mode 100644 index 00000000..e1b146a5 --- /dev/null +++ b/Setup/Patch/Abstract/AbstractConfigurationPathPatcher.php @@ -0,0 +1,102 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Setup\Patch\Abstract; + +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +abstract class AbstractConfigurationPathPatcher implements DataPatchInterface +{ + private ModuleDataSetupInterface $moduleDataSetup; + private WriterInterface $configWriter; + private ReinitableConfigInterface $reinitableConfig; + + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + WriterInterface $configWriter, + ReinitableConfigInterface $reinitableConfig + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->configWriter = $configWriter; + $this->reinitableConfig = $reinitableConfig; + } + + public function apply(): void + { + $this->moduleDataSetup->getConnection()->startSetup(); + + foreach (static::REPLACE_CONFIG_PATHS as $oldConfigPath => $newConfigPath) { + $this->updateConfigValue( + $this->moduleDataSetup, + $oldConfigPath, + $newConfigPath + ); + } + + $this->moduleDataSetup->getConnection()->endSetup(); + } + + private function updateConfigValue( + ModuleDataSetupInterface $setup, + string $oldPath, + string $newPath + ): void { + $config = $this->findConfig($setup, $oldPath); + + if ($config !== false) { + $this->configWriter->save( + $newPath, + $config['value'], + $config['scope'], + $config['scope_id'] + ); + } + + $this->reinitableConfig->reinit(); + } + + private function findConfig(ModuleDataSetupInterface $setup, string $path): mixed + { + $configDataTable = $setup->getTable('core_config_data'); + $connection = $setup->getConnection(); + + $select = $connection->select() + ->from($configDataTable) + ->where( + 'path = ?', + $path + ); + + $matchingConfigs = $connection->fetchAll($select); + return reset($matchingConfigs); + } + + /** + * @inheritdoc + */ + public function getAliases(): array + { + return []; + } + + /** + * @inheritdoc + */ + public static function getDependencies(): array + { + return []; + } +} diff --git a/Setup/Patch/Data/SeparateConfigurationFields.php b/Setup/Patch/Data/SeparateConfigurationFields.php new file mode 100644 index 00000000..1540f921 --- /dev/null +++ b/Setup/Patch/Data/SeparateConfigurationFields.php @@ -0,0 +1,36 @@ + + */ +declare(strict_types=1); + +namespace Adyen\ExpressCheckout\Setup\Patch\Data; + +use Adyen\ExpressCheckout\Setup\Patch\Abstract\AbstractConfigurationPathPatcher; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +class SeparateConfigurationFields extends AbstractConfigurationPathPatcher implements DataPatchInterface +{ + const REPLACE_CONFIG_PATHS = [ + 'payment/adyen_express/show_apple_pay_on' => 'payment/adyen_applepay/express_show_on', + 'payment/adyen_express/show_google_pay_on' => 'payment/adyen_googlepay/express_show_on', + 'payment/adyen_express/apple_pay_button_color' => 'payment/adyen_applepay/express_button_color' + ]; + + /** + * @inheritdoc + */ + public static function getDependencies(): array + { + return [ + UpdateExpressDBPaths::class + ]; + } +} diff --git a/Setup/Patch/Data/UpdateExpressDBPaths.php b/Setup/Patch/Data/UpdateExpressDBPaths.php index 294ef895..6053d94c 100644 --- a/Setup/Patch/Data/UpdateExpressDBPaths.php +++ b/Setup/Patch/Data/UpdateExpressDBPaths.php @@ -13,109 +13,14 @@ namespace Adyen\ExpressCheckout\Setup\Patch\Data; -use Magento\Framework\App\Config\ReinitableConfigInterface; -use Magento\Framework\App\Config\Storage\WriterInterface; -use Magento\Framework\Setup\ModuleDataSetupInterface; +use Adyen\ExpressCheckout\Setup\Patch\Abstract\AbstractConfigurationPathPatcher; use Magento\Framework\Setup\Patch\DataPatchInterface; -use Magento\Framework\Setup\Patch\PatchVersionInterface; -class UpdateExpressDBPaths implements DataPatchInterface, PatchVersionInterface +class UpdateExpressDBPaths extends AbstractConfigurationPathPatcher implements DataPatchInterface { - private ModuleDataSetupInterface $moduleDataSetup; - private WriterInterface $configWriter; - private ReinitableConfigInterface $reinitableConfig; - - public function __construct( - ModuleDataSetupInterface $moduleDataSetup, - WriterInterface $configWriter, - ReinitableConfigInterface $reinitableConfig - ) { - $this->moduleDataSetup = $moduleDataSetup; - $this->configWriter = $configWriter; - $this->reinitableConfig = $reinitableConfig; - } - - public function apply(): void - { - $this->moduleDataSetup->getConnection()->startSetup(); - - // Update Apple Pay config path - $this->updateConfigValue( - $this->moduleDataSetup, - 'payment/adyen_hpp/show_apple_pay_on', - 'payment/adyen_express/show_apple_pay_on' - ); - - // Update Google Pay config path - $this->updateConfigValue( - $this->moduleDataSetup, - 'payment/adyen_hpp/show_google_pay_on', - 'payment/adyen_express/show_google_pay_on' - ); - - // Update Google Pay config path - $this->updateConfigValue( - $this->moduleDataSetup, - 'payment/adyen_hpp/apple_pay_button_color', - 'payment/adyen_express/apple_pay_button_color' - ); - - $this->moduleDataSetup->getConnection()->endSetup(); - } - - private function updateConfigValue( - ModuleDataSetupInterface $setup, - string $oldPath, - string $newPath - ): void { - $config = $this->findConfig($setup, $oldPath); - - if ($config !== false) { - $this->configWriter->save( - $newPath, - $config['value'], - $config['scope'], - $config['scope_id'] - ); - } - - $this->reinitableConfig->reinit(); - } - - private function findConfig(ModuleDataSetupInterface $setup, string $path): mixed - { - $configDataTable = $setup->getTable('core_config_data'); - $connection = $setup->getConnection(); - - $select = $connection->select() - ->from($configDataTable) - ->where( - 'path = ?', - $path - ); - - $matchingConfigs = $connection->fetchAll($select); - return reset($matchingConfigs); - } - - /** - * @inheritdoc - */ - public function getAliases(): array - { - return []; - } - - /** - * @inheritdoc - */ - public static function getDependencies(): array - { - return []; - } - - public static function getVersion(): string - { - return '2.0.0'; - } + const REPLACE_CONFIG_PATHS = [ + 'payment/adyen_hpp/show_apple_pay_on' => 'payment/adyen_express/show_apple_pay_on', + 'payment/adyen_hpp/show_google_pay_on' => 'payment/adyen_express/show_google_pay_on', + 'payment/adyen_hpp/apple_pay_button_color' => 'payment/adyen_express/apple_pay_button_color' + ]; } diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..50aea0e7 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.1.0 \ No newline at end of file diff --git a/ViewModel/CheckoutConfig.php b/ViewModel/CheckoutConfig.php index 3c32c349..cfa85d70 100644 --- a/ViewModel/CheckoutConfig.php +++ b/ViewModel/CheckoutConfig.php @@ -14,6 +14,7 @@ namespace Adyen\ExpressCheckout\ViewModel; use Adyen\Payment\Model\Ui\AdyenGenericConfigProvider; +use Adyen\Payment\Helper\Data; use Magento\Framework\DataObject; use Magento\Framework\Event\ManagerInterface; use Magento\Framework\Exception\NoSuchEntityException; @@ -28,6 +29,11 @@ class CheckoutConfig implements ArgumentInterface */ private $adyenGenericConfigProvider; + /** + * @var Data + */ + private $adyenDataHelper; + /** * @var SerializerInterface */ @@ -45,17 +51,20 @@ class CheckoutConfig implements ArgumentInterface /** * @param AdyenGenericConfigProvider $adyenGenericConfigProvider + * @param Data $adyenDataHelper * @param SerializerInterface $serializer * @param StoreManagerInterface $storeManager * @param ManagerInterface $eventManager */ public function __construct( AdyenGenericConfigProvider $adyenGenericConfigProvider, + Data $adyenDataHelper, SerializerInterface $serializer, StoreManagerInterface $storeManager, ManagerInterface $eventManager ) { $this->adyenGenericConfigProvider = $adyenGenericConfigProvider; + $this->adyenDataHelper = $adyenDataHelper; $this->serializer = $serializer; $this->storeManager = $storeManager; $this->eventManager = $eventManager; @@ -95,4 +104,14 @@ private function getStoreCode(): string { return $this->storeManager->getStore()->getCode(); } + + /** + * Return application info values + * + * @return array + */ + public function getAdyenData(): array + { + return $this->adyenDataHelper->buildRequestHeaders(); + } } diff --git a/composer.json b/composer.json index 77a85e85..2e47c90c 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "adyen/adyen-magento2-expresscheckout", "description": "Official Adyen Magento2 plugin to add express payment method shortcuts.", "type": "magento2-module", - "version": "2.1.0", + "version": "2.2.0", "license": "MIT", "repositories": [ { @@ -11,7 +11,7 @@ } ], "require": { - "adyen/module-payment": "^9" + "adyen/module-payment": "^9.7.1" }, "require-dev": { "phpunit/phpunit": "*", diff --git a/etc/adminhtml/system/adyen_online_checkout.xml b/etc/adminhtml/system/adyen_online_checkout.xml index af098a49..d9c373d1 100755 --- a/etc/adminhtml/system/adyen_online_checkout.xml +++ b/etc/adminhtml/system/adyen_online_checkout.xml @@ -16,31 +16,41 @@ Magento\Config\Block\System\Config\Form\Fieldset - + Magento\Config\Block\System\Config\Form\Fieldset - + Adyen\ExpressCheckout\Model\Config\Source\ShortcutAreas 1 - payment/adyen_express/show_google_pay_on + payment/adyen_googlepay/express_show_on Magento\Config\Block\System\Config\Form\Fieldset - + Adyen\ExpressCheckout\Model\Config\Source\ShortcutAreas 1 - payment/adyen_express/show_apple_pay_on + payment/adyen_applepay/express_show_on Adyen\ExpressCheckout\Model\Config\Source\ApplePay\ButtonColor - payment/adyen_express/apple_pay_button_color + payment/adyen_applepay/express_button_color Apple Pay documentation.]]> + + + Magento\Config\Block\System\Config\Form\Fieldset + + + Adyen\ExpressCheckout\Model\Config\Source\ShortcutAreas + 1 + payment/adyen_paypal_express/express_show_on + + diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 00000000..011ab661 --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,43 @@ + + + + + + + 0 + AdyenPaymentPaypalExpressFacade + pending + PayPal + 0 + 0 + order + 0 + 1 + 1 + 1 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 0 + adyen-express-payment-method + + + + diff --git a/etc/di.xml b/etc/di.xml index 67990a77..243a907a 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -26,10 +26,15 @@ + + + + + @@ -58,6 +63,7 @@ applepay googlepay paywithgoogle + paypal @@ -68,4 +74,31 @@ + + + adyen_paypal_express + Magento\Payment\Block\Form + Adyen\Payment\Block\Info\PaymentMethodInfo + AdyenPaymentPaypalExpressValueHandlerPool + AdyenPaymentValidatorPool + AdyenPaymentCommandPool + + + + + + AdyenPaymentPaypalExpressConfigValueHandler + + + + + + AdyenPaymentPaypalExpressConfig + + + + + adyen_paypal_express + + diff --git a/etc/events.xml b/etc/events.xml index c52811a6..0093ab2d 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -17,4 +17,7 @@ + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 860b4d1a..93c4e0c5 100755 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -14,21 +14,31 @@ - Adyen_ExpressCheckout::applepay/shortcut.phtml + Adyen_ExpressCheckout::abstract/shortcut.phtml adyen.applepay.mini-cart adyen-applepay-mini-cart - AdyenPaymentHppFacade + AdyenPaymentApplePayFacade - Adyen_ExpressCheckout::googlepay/shortcut.phtml + Adyen_ExpressCheckout::abstract/shortcut.phtml adyen.googlepay.mini-cart adyen-googlepay-mini-cart - AdyenPaymentHppFacade + AdyenPaymentGooglepayFacade + + + + + + Adyen_ExpressCheckout::abstract/shortcut.phtml + adyen.paypal.mini-cart + adyen-paypal-mini-cart + + AdyenPaymentPaypalExpressFacade diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml index 0c66000f..d6b2b0d3 100644 --- a/etc/frontend/events.xml +++ b/etc/frontend/events.xml @@ -14,5 +14,6 @@ + diff --git a/etc/module.xml b/etc/module.xml index b5ae5670..c11fe601 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -11,7 +11,7 @@ */ --> - + diff --git a/etc/webapi.xml b/etc/webapi.xml index caf27607..d9b6624c 100644 --- a/etc/webapi.xml +++ b/etc/webapi.xml @@ -59,4 +59,36 @@ + + + + + + + + %adyen_cart_id% + + + + + + + + + + + + + + + + %adyen_cart_id% + + + + + + + + diff --git a/view/frontend/templates/abstract/shortcut.phtml b/view/frontend/templates/abstract/shortcut.phtml new file mode 100644 index 00000000..c035a714 --- /dev/null +++ b/view/frontend/templates/abstract/shortcut.phtml @@ -0,0 +1,15 @@ + + +
+
diff --git a/view/frontend/templates/applepay/shortcut.phtml b/view/frontend/templates/applepay/shortcut.phtml deleted file mode 100644 index cb23ef2a..00000000 --- a/view/frontend/templates/applepay/shortcut.phtml +++ /dev/null @@ -1,36 +0,0 @@ -getContainerId(), random_int(PHP_INT_MIN, PHP_INT_MAX)); -} catch (Exception $e) { - /** - * Exception only thrown if an appropriate source of randomness cannot be found. - * https://www.php.net/manual/en/function.random-int.php - */ - $id = 0; -} - -$config = [ - 'Adyen_ExpressCheckout/js/applepay/button' => [ - 'actionSuccess' => $block->getActionSuccess(), - 'storeCode' => $block->getStorecode(), - 'countryCode' => $block->getDefaultCountryCode(), - 'currency' => $block->getCurrency(), - 'merchantAccount' => $block->getMerchantAccount(), - 'format' => $block->getFormat(), - 'locale' => $block->getLocale(), - 'originkey' => $block->getOriginKey(), - 'checkoutenv' => $block->getCheckoutEnvironment(), - 'isProductView' => (bool) $block->getIsProductView(), - 'buttonColor' => $block->getButtonColor(), - ] -]; -?> - diff --git a/view/frontend/templates/checkout-config.phtml b/view/frontend/templates/checkout-config.phtml index 075d11e2..4edece6b 100644 --- a/view/frontend/templates/checkout-config.phtml +++ b/view/frontend/templates/checkout-config.phtml @@ -5,9 +5,12 @@ getSerializedCheckoutConfig(); $checkoutConfig = __('window.checkoutConfig = %1;', $serializedCheckoutConfig); +$adyenData = $viewModel->getAdyenData(); +$serializedAdyenData = json_encode($adyenData, JSON_HEX_TAG); ?> diff --git a/view/frontend/templates/googlepay/shortcut.phtml b/view/frontend/templates/googlepay/shortcut.phtml deleted file mode 100644 index 06ddd3c2..00000000 --- a/view/frontend/templates/googlepay/shortcut.phtml +++ /dev/null @@ -1,34 +0,0 @@ -getContainerId(), random_int(PHP_INT_MIN, PHP_INT_MAX)); -} catch (Exception $e) { - /** - * Exception only thrown if an appropriate source of randomness cannot be found. - * https://www.php.net/manual/en/function.random-int.php - */ - $id = 0; -} - -$config = [ - 'Adyen_ExpressCheckout/js/googlepay/button' => [ - 'actionSuccess' => $block->getActionSuccess(), - 'storeCode' => $block->getStorecode(), - 'countryCode' => $block->getDefaultCountryCode(), - 'currency' => $block->getCurrency(), - 'merchantAccount' => $block->getMerchantAccount(), - 'format' => $block->getFormat(), - 'locale' => $block->getLocale(), - 'originkey' => $block->getOriginKey(), - 'checkoutenv' => $block->getCheckoutEnvironment(), - 'isProductView' => (bool) $block->getIsProductView() - ] -]; -?> -
-
-
diff --git a/view/frontend/web/css/source/_module.less b/view/frontend/web/css/source/_module.less index 62990517..0b9712ff 100644 --- a/view/frontend/web/css/source/_module.less +++ b/view/frontend/web/css/source/_module.less @@ -1,5 +1,5 @@ & when (@media-common = true) { - .google-pay-button-card .gpay-card-info-container { + .googlepay-button-card .gpay-card-info-container { min-width: auto; width: 100%; } diff --git a/view/frontend/web/js/actions/createOrder.js b/view/frontend/web/js/actions/createOrder.js new file mode 100644 index 00000000..f4e72668 --- /dev/null +++ b/view/frontend/web/js/actions/createOrder.js @@ -0,0 +1,18 @@ +define([ + 'mage/storage', + 'Adyen_ExpressCheckout/js/helpers/getApiUrl' +], function (storage, getApiUrl) { + 'use strict'; + return function (payload, isProductView) { + return storage.put( + getApiUrl('order', isProductView), + payload, + false + ).then(function(response) { + // Assuming response contains orderId + return response; + }).catch(function(response) { + throw new Error('Failed to place order'); + }); + }; +}); diff --git a/view/frontend/web/js/actions/initPayments.js b/view/frontend/web/js/actions/initPayments.js new file mode 100644 index 00000000..b81dd5f1 --- /dev/null +++ b/view/frontend/web/js/actions/initPayments.js @@ -0,0 +1,36 @@ +define([ + 'mage/storage', + 'Adyen_ExpressCheckout/js/helpers/getIsLoggedIn', + 'Adyen_ExpressCheckout/js/helpers/getMaskedIdFromCart', + 'Adyen_ExpressCheckout/js/model/maskedId' +], function ( + storage, + getIsLoggedIn, + getMaskedIdFromCart, + maskedIdModel +) { + 'use strict'; + + return function (paymentData, isProductView) { + let payload= { + stateData: JSON.stringify(paymentData) + }; + + const url = getIsLoggedIn() + ? 'rest/V1/adyen/express/init-payments/mine' + : 'rest/V1/adyen/express/init-payments/guest'; + + if (isProductView) { + payload.adyenMaskedQuoteId = maskedIdModel().getMaskedId(); + } else { + payload.guestMaskedId = getMaskedIdFromCart(); + } + + return new Promise(function (resolve, reject) { + storage.post( + url, + JSON.stringify(payload) + ).done(resolve).fail(reject); + }); + }; +}); diff --git a/view/frontend/web/js/actions/updatePaypalOrder.js b/view/frontend/web/js/actions/updatePaypalOrder.js new file mode 100755 index 00000000..6d59ac84 --- /dev/null +++ b/view/frontend/web/js/actions/updatePaypalOrder.js @@ -0,0 +1,78 @@ +define([ + 'mage/storage', + 'Magento_Checkout/js/model/url-builder', + 'Adyen_ExpressCheckout/js/helpers/getIsLoggedIn', + 'Adyen_ExpressCheckout/js/model/maskedId', + 'Adyen_ExpressCheckout/js/helpers/getMaskedIdFromCart', +], function ( + storage, + urlBuilder, + getIsLoggedIn, + maskedIdModel, + getMaskedIdFromCart +) { + 'use strict'; + + function updateOrder(isProductView, paymentData, shippingMethods, currency, selectedShippingMethod = null) { + let updateOrderUrl = getIsLoggedIn() + ? urlBuilder.createUrl('/adyen/express/paypal-update-order/mine', {}) + : urlBuilder.createUrl('/adyen/express/paypal-update-order/guest', {}); + + const updateOrderPayload = { + paymentData: paymentData + }; + + if (isProductView) { + updateOrderPayload.adyenMaskedQuoteId = maskedIdModel().getMaskedId(); + } else { + updateOrderPayload.guestMaskedId = getMaskedIdFromCart(); + } + + let deliveryMethods = []; + if (selectedShippingMethod) { + let method = { + reference: selectedShippingMethod.id, + description: selectedShippingMethod.label, + type: 'Shipping', + amount: { + currency: currency, + value: Math.round(selectedShippingMethod.amount.value * 100) + }, + selected: true + }; + deliveryMethods.push(method); + updateOrderPayload.deliveryMethods = JSON.stringify(deliveryMethods); + } + else { + for (let i = 0; i < shippingMethods.length; i++) { + let method = { + reference: (i + 1).toString(), + description: shippingMethods[i].detail, + type: 'Shipping', + amount: { + currency: currency, + value: Math.round(shippingMethods[i].amount * 100) + }, + selected: i === 0 + }; + // Add method object to array. + deliveryMethods.push(method); + } + updateOrderPayload.deliveryMethods = JSON.stringify(deliveryMethods); + } + + return storage.post( + updateOrderUrl, + JSON.stringify(updateOrderPayload) + ).then(function (response) { + return response; + }).catch(function (error) { + console.error('Failed to update PayPal order:', error); + throw error; + }); + } + + return { + updateOrder: updateOrder + }; +}); diff --git a/view/frontend/web/js/applepay/button.js b/view/frontend/web/js/applepay/button.js index 08a77e8c..325b9fb9 100644 --- a/view/frontend/web/js/applepay/button.js +++ b/view/frontend/web/js/applepay/button.js @@ -29,6 +29,7 @@ define([ 'Adyen_ExpressCheckout/js/model/countries', 'Adyen_ExpressCheckout/js/model/totals', 'Adyen_ExpressCheckout/js/model/currency', + 'Adyen_ExpressCheckout/js/helpers/getCurrentPage', 'Adyen_ExpressCheckout/js/model/virtualQuote' ], function ( @@ -62,6 +63,7 @@ define([ countriesModel, totalsModel, currencyModel, + getCurrentPage, virtualQuoteModel ) { 'use strict'; @@ -95,7 +97,7 @@ define([ setExpressMethods(response); totalsModel().setTotal(response.totals.grand_total); - currencyModel().setCurrency(response.totals.quote_currency_code) + currencyModel().setCurrency(response.totals.quote_currency_code); const $priceBox = getPdpPriceBox(); const pdpForm = getPdpForm(element); @@ -142,13 +144,31 @@ define([ initialiseApplePayComponent: async function (applePaymentMethod, element) { const config = configModel().getConfig(); + const adyenData = window.adyenData; + let currentPage = getCurrentPage(this.isProductView, element); + const adyenCheckoutComponent = await new AdyenCheckout({ locale: config.locale, - originKey: config.originkey, environment: config.checkoutenv, + analytics: { + analyticsData: { + applicationInfo: { + merchantApplication: { + name: adyenData['merchant-application-name'], + version: adyenData['merchant-application-version'] + }, + externalPlatform: { + name: adyenData['external-platform-name'], + version: adyenData['external-platform-version'] + } + } + } + }, risk: { enabled: false }, + isExpress: true, + expressPage: currentPage, clientKey: AdyenConfiguration.getClientKey() }); const applePayConfiguration = this.getApplePayConfiguration(applePaymentMethod, element); @@ -249,6 +269,7 @@ define([ : formatAmount(getCartSubtotal() * 100), currency: currency }, + isExpress: true, supportedNetworks: getSupportedNetworks(), merchantCapabilities: ['supports3DS'], requiredShippingContactFields: ['postalAddress', 'name', 'email', 'phone'], diff --git a/view/frontend/web/js/googlepay/button.js b/view/frontend/web/js/googlepay/button.js index 9aea73ef..a6eeb6ea 100644 --- a/view/frontend/web/js/googlepay/button.js +++ b/view/frontend/web/js/googlepay/button.js @@ -37,7 +37,8 @@ define([ 'Adyen_ExpressCheckout/js/model/countries', 'Adyen_ExpressCheckout/js/model/totals', 'Adyen_ExpressCheckout/js/model/currency', - 'Adyen_ExpressCheckout/js/model/virtualQuote' + 'Adyen_ExpressCheckout/js/model/virtualQuote', + 'Adyen_ExpressCheckout/js/helpers/getCurrentPage' ], function ( $, @@ -78,7 +79,8 @@ define([ countriesModel, totalsModel, currencyModel, - virtualQuoteModel + virtualQuoteModel, + getCurrentPage ) { 'use strict'; @@ -142,7 +144,7 @@ define([ setExpressMethods(response); totalsModel().setTotal(response.totals.grand_total); - currencyModel().setCurrency(response.totals.quote_currency_code) + currencyModel().setCurrency(response.totals.quote_currency_code); const $priceBox = getPdpPriceBox(); const pdpForm = getPdpForm(element); @@ -171,12 +173,31 @@ define([ initialiseGooglePayComponent: async function (googlePaymentMethod, element) { const config = configModel().getConfig(); + const adyenData = window.adyenData; + let currentPage = getCurrentPage(this.isProductView, element); + this.checkoutComponent = await new AdyenCheckout({ locale: config.locale, clientKey: config.originkey, environment: config.checkoutenv, + analytics: { + analyticsData: { + applicationInfo: { + merchantApplication: { + name: adyenData['merchant-application-name'], + version: adyenData['merchant-application-version'] + }, + externalPlatform: { + name: adyenData['external-platform-name'], + version: adyenData['external-platform-version'] + } + } + } + }, paymentMethodsResponse: getPaymentMethod('googlepay', this.isProductView), onAdditionalDetails: this.handleOnAdditionalDetails.bind(this), + isExpress: true, + expressPage: currentPage, risk: { enabled: false } @@ -256,6 +277,7 @@ define([ format: 'FULL', phoneNumberRequired: true }, + isExpress: true, callbackIntents: !isVirtual ? ['SHIPPING_ADDRESS', 'SHIPPING_OPTION'] : ['OFFER'], transactionInfo: { totalPriceStatus: 'ESTIMATED', diff --git a/view/frontend/web/js/helpers/getCurrentPage.js b/view/frontend/web/js/helpers/getCurrentPage.js new file mode 100644 index 00000000..e86a7289 --- /dev/null +++ b/view/frontend/web/js/helpers/getCurrentPage.js @@ -0,0 +1,19 @@ +define(['jquery',], function ($) { + 'use strict'; + + return function (isProductView, element) { + let currentPage = ''; + + if (isProductView) { + currentPage = 'pdp'; + } else if ($(element).closest('.minicart-wrapper').length > 0) { + currentPage = 'minicart'; + } else if ($('.cart-container').length > 0) { + currentPage = 'cart'; + } else if ($('body').hasClass('checkout-index-index')) { + currentPage = 'checkout'; + } + + return currentPage; + }; +}); diff --git a/view/frontend/web/js/helpers/getPaypalStyles.js b/view/frontend/web/js/helpers/getPaypalStyles.js new file mode 100644 index 00000000..2c59d563 --- /dev/null +++ b/view/frontend/web/js/helpers/getPaypalStyles.js @@ -0,0 +1,11 @@ +define(function () { + 'use strict'; + + return function () { + // Default styles that can be overridden by themes. + return { + buttonColor: 'black', + buttonType: 'long' + }; + }; +}); diff --git a/view/frontend/web/js/model/adyen-payment-service.js b/view/frontend/web/js/model/adyen-payment-service.js new file mode 100644 index 00000000..e23d8ee2 --- /dev/null +++ b/view/frontend/web/js/model/adyen-payment-service.js @@ -0,0 +1,55 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define( + [ + 'jquery', + 'underscore', + 'Magento_Checkout/js/model/quote', + 'Magento_Checkout/js/model/url-builder', + 'Adyen_ExpressCheckout/js/helpers/getIsLoggedIn', + 'mage/storage' + ], + function( + $, + _, + quote, + urlBuilder, + getIsLoggedIn, + storage + ){ + 'use strict'; + + function paymentDetails(data, orderId, quoteId = null) { + let serviceUrl; + let payload = { + 'payload': JSON.stringify(data), + 'orderId': orderId, + 'quoteId': quoteId + }; + const isLoggedIn = getIsLoggedIn(); + if (isLoggedIn) { + serviceUrl = urlBuilder.createUrl( + '/adyen/carts/mine/payments-details', + {} + ); + } else { + serviceUrl = urlBuilder.createUrl( + '/adyen/guest-carts/:cartId/payments-details', { + cartId: quoteId ?? quote.getQuoteId() + } + ); + } + + return storage.post( + serviceUrl, + JSON.stringify(payload), + true + ); + } + return { + paymentDetails: paymentDetails + }; + } +); diff --git a/view/frontend/web/js/paypal_express/button.js b/view/frontend/web/js/paypal_express/button.js new file mode 100755 index 00000000..cbff53cb --- /dev/null +++ b/view/frontend/web/js/paypal_express/button.js @@ -0,0 +1,640 @@ +define([ + 'uiComponent', + 'mage/translate', + 'Magento_Customer/js/customer-data', + 'Adyen_Payment/js/model/adyen-configuration', + 'Adyen_Payment/js/adyen', + 'Adyen_ExpressCheckout/js/actions/activateCart', + 'Adyen_ExpressCheckout/js/actions/createOrder', + 'Adyen_ExpressCheckout/js/actions/getShippingMethods', + 'Adyen_ExpressCheckout/js/actions/getExpressMethods', + 'Adyen_ExpressCheckout/js/actions/setShippingInformation', + 'Adyen_ExpressCheckout/js/actions/setTotalsInfo', + 'Adyen_ExpressCheckout/js/helpers/formatAmount', + 'Adyen_ExpressCheckout/js/helpers/getPaypalStyles', + 'Adyen_ExpressCheckout/js/helpers/getCartSubtotal', + 'Adyen_ExpressCheckout/js/helpers/getPaymentMethod', + 'Adyen_ExpressCheckout/js/helpers/getPdpForm', + 'Adyen_ExpressCheckout/js/helpers/getPdpPriceBox', + 'Adyen_ExpressCheckout/js/helpers/isConfigSet', + 'Adyen_ExpressCheckout/js/helpers/getRegionId', + 'Adyen_ExpressCheckout/js/helpers/redirectToSuccess', + 'Adyen_ExpressCheckout/js/helpers/setExpressMethods', + 'Adyen_ExpressCheckout/js/helpers/validatePdpForm', + 'Adyen_ExpressCheckout/js/model/config', + 'Adyen_ExpressCheckout/js/model/countries', + 'Adyen_ExpressCheckout/js/model/totals', + 'Adyen_ExpressCheckout/js/model/currency', + 'knockout', + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/model/quote', + 'Adyen_ExpressCheckout/js/actions/initPayments', + 'Adyen_ExpressCheckout/js/actions/updatePaypalOrder', + 'Adyen_ExpressCheckout/js/actions/setBillingAddress', + 'Magento_Checkout/js/model/full-screen-loader', + 'Adyen_ExpressCheckout/js/model/adyen-payment-service', + 'jquery', + 'Adyen_ExpressCheckout/js/model/virtualQuote', + 'Adyen_ExpressCheckout/js/model/maskedId', + 'Adyen_ExpressCheckout/js/helpers/getCurrentPage', + 'Adyen_ExpressCheckout/js/helpers/getMaskedIdFromCart', +], function ( + Component, + $t, + customerData, + AdyenConfiguration, + AdyenCheckout, + activateCart, + createOrder, + getShippingMethods, + getExpressMethods, + setShippingInformation, + setTotalsInfo, + formatAmount, + getPaypalStyles, + getCartSubtotal, + getPaymentMethod, + getPdpForm, + getPdpPriceBox, + isConfigSet, + getRegionId, + redirectToSuccess, + setExpressMethods, + validatePdpForm, + configModel, + countriesModel, + totalsModel, + currencyModel, + ko, + customer, + quote, + initPayments, + updatePaypalOrder, + setBillingAddress, + fullScreenLoader, + adyenPaymentService, + $, + virtualQuoteModel, + maskedIdModel, + getCurrentPage, + getMaskedIdFromCart +) { + 'use strict'; + + return Component.extend({ + isPlaceOrderActionAllowed: ko.observable( + quote.billingAddress() != null), + + defaults: { + shippingMethods: {}, + isProductView: false, + maskedId: null, + paypalComponent: null, + shippingAddress: {}, + shippingMethod: null, + shopperEmail: null, + billingAddress : {}, + orderId: null, + quoteId: null + }, + + initialize: async function (config, element) { + this._super(); + + // Set the config and countries model + configModel().setConfig(config); + countriesModel(); + + // Determine if this is a product view page + this.isProductView = config.isProductView; + + if (!this.isProductView) { + // Retrieve the PayPal payment method + let paypalPaymentMethod = await getPaymentMethod('paypal', this.isProductView); + virtualQuoteModel().setIsVirtual(false); + + if (!paypalPaymentMethod) { + // Subscribe to cart updates if PayPal method is not immediately available + const cart = customerData.get('cart'); + cart.subscribe(function () { + this.reloadPaypalButton(element); + }.bind(this)); + } else { + // Initialize the PayPal component if config is set + if (!isConfigSet(paypalPaymentMethod, ['merchantId'])) { + return; + } + this.initialisePaypalComponent(paypalPaymentMethod, element); + } + } else { + // Initialize PayPal component on product view page + this.initialiseonPDP(config, element); + } + }, + + initialiseonPDP: async function (config, element) { + // Configuration setup + try { + const response = await getExpressMethods().getRequest(element); + const cart = customerData.get('cart'); + + virtualQuoteModel().setIsVirtual(true, response); + + cart.subscribe(function () { + this.reloadPaypalButton(element); + }.bind(this)); + + setExpressMethods(response); + totalsModel().setTotal(response.totals.grand_total); + currencyModel().setCurrency(response.totals.quote_currency_code); + + const $priceBox = getPdpPriceBox(); + const pdpForm = getPdpForm(element); + + $priceBox.on('priceUpdated', async function () { + const isValid = new Promise((resolve, reject) => { + return validatePdpForm(resolve, reject, pdpForm, true); + }); + + isValid + .then(async function () { + this.reloadPaypalButton(element); + }.bind(this)) + .catch(function (error) { + console.log(error); + }); + }.bind(this)); + + let paypalPaymentMethod = await getPaymentMethod('paypal', this.isProductView); + + if (!paypalPaymentMethod) { + console.error('PayPal payment method not found'); + return; + } + + await this.initialisePaypalComponent(paypalPaymentMethod, element); + } catch (error) { + console.error('Error in initialiseonPDP:', error); + } + }, + + + initialisePaypalComponent: async function (paypalPaymentMethod, element) { + // Configuration setup + const config = configModel().getConfig(); + const adyenData = window.adyenData; + let currentPage = getCurrentPage(this.isProductView, element); + + const adyenCheckoutComponent = await new AdyenCheckout({ + locale: config.locale, + originKey: config.originkey, + environment: config.checkoutenv, + analytics: { + analyticsData: { + applicationInfo: { + merchantApplication: { + name: adyenData['merchant-application-name'], + version: adyenData['merchant-application-version'] + }, + externalPlatform: { + name: adyenData['external-platform-name'], + version: adyenData['external-platform-version'] + } + } + } + }, + risk: { + enabled: false + }, + isExpress: true, + expressPage: currentPage, + clientKey: AdyenConfiguration.getClientKey() + }); + + const paypalConfiguration = this.getPaypalConfiguration(paypalPaymentMethod, element); + + if (this.isProductView) { + paypalConfiguration.currencyCode = currencyModel().getCurrency(); + paypalConfiguration.amount.currency = currencyModel().getCurrency(); + } + + try { + this.paypalComponent = adyenCheckoutComponent.create('paypal', paypalConfiguration); + + if (typeof this.paypalComponent.isAvailable === 'function') { + this.paypalComponent + .isAvailable() + .then(() => { + this.onAvailable(element); + }) + .catch((e) => { + this.onNotAvailable(e); + }); + } else { + this.onAvailable(element); + } + } catch (error) { + console.error('Error creating PayPal component', error); + } + }, + + onNotAvailable: function (error) { + console.log('PayPal is unavailable.', error); + }, + + onAvailable: function (element) { + element.style.display = 'block'; + this.paypalComponent.mount(element); + }, + + unmountPaypal: function () { + if (this.paypalComponent) { + this.paypalComponent.unmount(); + } + }, + + reloadPaypalButton: async function (element) { + const paypalPaymentMethod = await getPaymentMethod('paypal', this.isProductView); + + if (this.isProductView) { + const pdpResponse = await getExpressMethods().getRequest(element); + + virtualQuoteModel().setIsVirtual(true, pdpResponse); + setExpressMethods(pdpResponse); + totalsModel().setTotal(pdpResponse.totals.grand_total); + } else { + virtualQuoteModel().setIsVirtual(false); + } + + this.unmountPaypal(); + + if (!isConfigSet(paypalPaymentMethod, ['merchantId'])) { + return; + } + + this.initialisePaypalComponent(paypalPaymentMethod, element); + }, + + getPaypalConfiguration: function (paypalPaymentMethod, element) { + const paypalStyles = getPaypalStyles(); + const config = configModel().getConfig(); + const countryCode = config.countryCode; + + let currency; + let paypalBaseConfiguration; + + if (this.isProductView) { + currency = currencyModel().getCurrency(); + } else { + const cartData = customerData.get('cart'); + const adyenMethods = cartData()['adyen_payment_methods']; + const paymentMethodExtraDetails = adyenMethods.paymentMethodsExtraDetails[paypalPaymentMethod.type]; + currency = paymentMethodExtraDetails.configuration.amount.currency; + } + + paypalBaseConfiguration = { + countryCode: countryCode, + environment: config.checkoutenv.toUpperCase(), + isExpress: true, + configuration: paypalPaymentMethod.configuration, + amount: { + currency: currency, + value: this.isProductView + ? formatAmount(totalsModel().getTotal() * 100) + : formatAmount(getCartSubtotal() * 100) + }, + onSubmit: (state, component) => { + const paymentData = state.data; + + paymentData.merchantAccount = config.merchantAccount; + initPayments(paymentData, this.isProductView).then((responseJSON) => { + let response = JSON.parse(responseJSON); + if (response.action) { + component.handleAction(response.action); + } else { + console.log('Init Payments call failed', response); + } + }).catch((error) => { + console.error('Payment initiation failed', error); + }); + }, + onShippingAddressChange: async (data, actions, component) => { + try { + this.shippingAddress = data.shippingAddress; + if(this.isProductView) { + await activateCart(this.isProductView); + } + + const shippingMethods = await this.getShippingMethods(data.shippingAddress); + let shippingMethod = shippingMethods.find(method => method.identifier === this.shippingMethod); + await this.setShippingAndTotals(shippingMethod, data.shippingAddress); + + const currentPaymentData = component.paymentData; + + await updatePaypalOrder.updateOrder( + this.isProductView, + currentPaymentData, + shippingMethods, + currency + ).then(function (response) { + let parsedResponse = JSON.parse(response); + component.updatePaymentData(parsedResponse.paymentData); + }).catch(function () { + component.updatePaymentData(currentPaymentData); + return actions.reject(); + }); + } catch (error) { + return actions.reject(); + } + }, + onShippingOptionsChange: async (data, actions, component) => { + let shippingMethod = []; + const currentPaymentData = component.paymentData; + for (const method of Object.values(this.shippingMethods)) { + if (method.carrier_title === data.selectedShippingOption.label) { + this.shippingMethod = method.method_code; + shippingMethod = { + identifier: method.method_code, + label: method.method_title, + detail: method.carrier_title ? method.carrier_title : '', + amount: parseFloat(method.amount).toFixed(2), + carrierCode: method.carrier_code, + }; + break; + } + } + await this.setShippingAndTotals(shippingMethod, this.shippingAddress); + + await updatePaypalOrder.updateOrder( + this.isProductView, + currentPaymentData, + this.shippingMethods, + currency, + data.selectedShippingOption + ).then(function (response) { + let parsedResponse = JSON.parse(response); + component.updatePaymentData(parsedResponse.paymentData); + }).catch(function () { + component.updatePaymentData(currentPaymentData); + return actions.reject(); + }); + }, + onShopperDetails: async (shopperDetails, rawData, actions) => { + try { + const isVirtual = virtualQuoteModel().getIsVirtual(); + + const { billingAddress, shippingAddress } = await this.setupAddresses(shopperDetails); + + let billingAddressPayload = { + address: billingAddress, + 'useForShipping': false + }; + + let shippingInformationPayload = { + addressInformation: { + shipping_address: shippingAddress, + billing_address: billingAddress, + shipping_method_code: this.shippingMethod, + shipping_carrier_code: this.shippingMethods[this.shippingMethod].carrier_code + } + }; + activateCart(this.isProductView) + .then(() => { + return setBillingAddress(billingAddressPayload, this.isProductView); + }) + .then(() => { + if (!isVirtual) { + return setShippingInformation(shippingInformationPayload, this.isProductView) + .then(() => { + return this.createOrder(); + }) + .then(() => { + actions.resolve(); + }); + } else { + return this.createOrder().then(() => { + actions.resolve(); + }); + } + }) + .catch((error) => { + console.error('An error occurred:', error); + }); + } catch (error) { + console.error('Failed to complete order:', error); + actions.reject(); + } + }, + + onAdditionalDetails: async (state, component) => { + let request = state.data; + let self = this; + fullScreenLoader.startLoader(); + request.orderId = this.orderId; + + let quoteId = this.isProductView ? maskedIdModel().getMaskedId() : getMaskedIdFromCart(); + + adyenPaymentService.paymentDetails(request, this.orderId, quoteId). + done(function(responseJSON) { + fullScreenLoader.stopLoader(); + self.handleAdyenResult(responseJSON, self.orderId); + }). + fail(function(response) { + self.isPlaceOrderActionAllowed(true); //Complete this function + fullScreenLoader.stopLoader(); + }); + + }, + style: paypalStyles + }; + + return paypalBaseConfiguration; + }, + + setupAddresses: async function (shopperDetails) { + let billingAddress = { + 'email': shopperDetails.shopperEmail, + 'telephone': shopperDetails.telephoneNumber, + 'firstname': shopperDetails.shopperName.firstName, + 'lastname': shopperDetails.shopperName.lastName, + 'street': [ + shopperDetails.billingAddress.street + ], + 'city': shopperDetails.billingAddress.city, + 'region': shopperDetails.billingAddress.stateOrProvince, + 'region_id': getRegionId(shopperDetails.billingAddress.country, shopperDetails.billingAddress.stateOrProvince), + 'region_code': null, + 'country_id': shopperDetails.billingAddress.country.toUpperCase(), + 'postcode': shopperDetails.billingAddress.postalCode, + 'same_as_billing': 0, + 'customer_address_id': 0, + 'save_in_address_book': 0 + }; + + let shippingAddress = { + 'email': shopperDetails.shopperEmail, + 'telephone': shopperDetails.telephoneNumber, + 'firstname': shopperDetails.shopperName.firstName, + 'lastname': shopperDetails.shopperName.lastName, + 'street': [ + shopperDetails.shippingAddress.street + ], + 'city': shopperDetails.shippingAddress.city, + 'region': shopperDetails.shippingAddress.stateOrProvince, + 'region_id': getRegionId(shopperDetails.shippingAddress.country, shopperDetails.shippingAddress.stateOrProvince), + 'region_code': null, + 'country_id': shopperDetails.shippingAddress.country.toUpperCase(), + 'postcode': shopperDetails.shippingAddress.postalCode, + 'same_as_billing': 0, + 'customer_address_id': 0, + 'save_in_address_book': 0 + }; + + return { + billingAddress: billingAddress, + shippingAddress: shippingAddress + }; + }, + + // Extracted method to get shipping methods + getShippingMethods: function (shippingAddress) { + const payload = { + address: { + country_id: shippingAddress.countryCode, + postcode: shippingAddress.postalCode, + street: [''] + } + }; + + return new Promise((resolve, reject) => { + getShippingMethods(payload, this.isProductView).then(result => { + if (result.length === 0) { + reject(new Error($t('There are no shipping methods available for you right now. Please try again or use an alternative payment method.'))); + } + + let shippingMethods = []; + + for (let method of result) { + if (typeof method.method_code !== 'string') { + continue; + } + let shippingMethod = { + identifier: method.method_code, + label: method.method_title, + detail: method.carrier_title ? method.carrier_title : '', + amount: parseFloat(method.amount).toFixed(2), + carrierCode: method.carrier_code, + }; + shippingMethods.push(shippingMethod); + this.shippingMethods[method.method_code] = method; + if (!this.shippingMethod) { + this.shippingMethod = method.method_code; + } + } + resolve(shippingMethods); + }).catch(error => { + console.error('Failed to retrieve shipping methods:', error); + reject(new Error($t('Failed to retrieve shipping methods. Please try again later.'))); + }); + }); + }, + createOrder: function(email) { + const payload = { + paymentMethod: { + method: 'adyen_paypal_express', + additional_data: [ + 'brand_code:paypal' + ] + } + }; + + if (window.checkout && window.checkout.agreementIds) { + payload.paymentMethod.extension_attributes = { + agreement_ids: window.checkout.agreementIds + }; + } + + return new Promise((resolve, reject) => { + createOrder(JSON.stringify(payload), this.isProductView) + .then(function(orderId) { + if (orderId) { + this.orderId = orderId; + resolve(orderId); + } else { + reject(new Error('Order ID not returned')); + } + }.bind(this)) + .catch(function(e) { + console.error('Adyen Paypal Unable to take payment', e); + reject(e); + }); + }); + }, + + handleAdyenResult: function (responseJSON, orderId) { + let self = this; + let response = JSON.parse(responseJSON); + + if (response.isFinal) { + // Status is final redirect to the success page + redirectToSuccess(); + } else { + // Handle action + self.handleAction(response.action, orderId); // Complete this + } + }, + + handleAction: function(action, orderId) { + let self = this; + let popupModal; + + fullScreenLoader.stopLoader(); + + if (action.type === 'threeDS2' || action.type === 'await') { + popupModal = self.showModal(); + } + + try { + self.adyenCheckoutComponent.createFromAction(action).mount('#' + this.modalLabel); + } catch (e) { + console.log(e); + self.closeModal(popupModal); + } + }, + + // Extracted method to set shipping information and totals + setShippingAndTotals: function (shippingMethod, shippingAddress) { + let address = { + 'countryId': shippingAddress.countryCode, + 'region': shippingAddress.state, + 'regionId': getRegionId(shippingAddress.country_id, shippingAddress.state), + 'postcode': shippingAddress.postalCode + }; + + let shippingInformationPayload = { + addressInformation: { + shipping_address: address, + billing_address: address, + shipping_method_code: this.shippingMethod, + shipping_carrier_code: shippingMethod ? shippingMethod.carrierCode : '' + } + }; + + let totalsPayload = { + 'addressInformation': { + 'address': address, + 'shipping_method_code': this.shippingMethod, + 'shipping_carrier_code': shippingMethod ? shippingMethod.carrierCode : '' + } + }; + + return Promise.all([ + setShippingInformation(shippingInformationPayload, this.isProductView), + setTotalsInfo(totalsPayload, this.isProductView) + ]).catch(error => { + console.error('Failed to set shipping and totals information:', error); + throw new Error($t('Failed to set shipping and totals information. Please try again later.')); + }); + } + }); +});