diff --git a/etc/csp_whitelist.xml b/etc/csp_whitelist.xml index 1de54c94f..6e1bb7674 100644 --- a/etc/csp_whitelist.xml +++ b/etc/csp_whitelist.xml @@ -4,6 +4,7 @@ *.adyen.com + *.payments-amazon.com @@ -14,16 +15,20 @@ *.adyen.com + *.payments-amazon.com + *.media-amazon.com *.adyen.com + payments-eu.amazon.com *.adyen.com + *.amazon.de diff --git a/view/frontend/web/js/model/adyen-checkout.js b/view/frontend/web/js/model/adyen-checkout.js index c64675483..3fbdcc723 100644 --- a/view/frontend/web/js/model/adyen-checkout.js +++ b/view/frontend/web/js/model/adyen-checkout.js @@ -19,7 +19,13 @@ define( ) { 'use strict'; return { - buildCheckoutComponent: function (paymentMethodsResponse, handleOnAdditionalDetails, handleOnCancel = undefined, handleOnSubmit = undefined) { + buildCheckoutComponent: function ( + paymentMethodsResponse, + handleOnAdditionalDetails, + handleOnCancel = undefined, + handleOnSubmit = undefined, + handleOnError = undefined + ) { if (!!paymentMethodsResponse.paymentMethodsResponse) { return AdyenCheckout({ locale: adyenConfiguration.getLocale(), @@ -28,7 +34,8 @@ define( paymentMethodsResponse: paymentMethodsResponse.paymentMethodsResponse, onAdditionalDetails: handleOnAdditionalDetails, onCancel: handleOnCancel, - onSubmit: handleOnSubmit + onSubmit: handleOnSubmit, + onError: handleOnError } ); } else { diff --git a/view/frontend/web/js/view/payment/method-renderer/adyen-amazonpay-method.js b/view/frontend/web/js/view/payment/method-renderer/adyen-amazonpay-method.js index b8fe8d38d..3111c8b50 100644 --- a/view/frontend/web/js/view/payment/method-renderer/adyen-amazonpay-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-amazonpay-method.js @@ -11,24 +11,33 @@ define( [ 'Magento_Checkout/js/model/quote', 'Adyen_Payment/js/view/payment/method-renderer/adyen-pm-method', - 'Adyen_Payment/js/model/adyen-checkout' + 'Adyen_Payment/js/model/adyen-checkout', + 'Adyen_Payment/js/model/adyen-payment-service', + 'mage/url' ], function( quote, adyenPaymentMethod, - adyenCheckout + adyenCheckout, + adyenPaymentService, + urlBuilder ) { const amazonSessionKey = 'amazonCheckoutSessionId'; return adyenPaymentMethod.extend({ placeOrderButtonVisible: false, - initialize: function () { - this._super(); - }, + amazonPayComponent: null, + buildComponentConfiguration: function (paymentMethod, paymentMethodsExtraInfo) { let self = this; let formattedShippingAddress = {}; let formattedBillingAddress = {}; let baseComponentConfiguration = this._super(); + + baseComponentConfiguration = Object.assign( + baseComponentConfiguration, + paymentMethodsExtraInfo[paymentMethod.type].configuration + ); + if (!quote.isVirtual() && !!quote.shippingAddress()) { formattedShippingAddress = self.getFormattedAddress(quote.shippingAddress()); } @@ -36,7 +45,7 @@ define( if (!!quote.billingAddress()) { formattedBillingAddress = self.getFormattedAddress(quote.billingAddress()); } - baseComponentConfiguration.showPayButton = true; + baseComponentConfiguration.onClick = function(resolve,reject) { if (self.validate()) { resolve(); @@ -44,19 +53,15 @@ define( reject(); } } - baseComponentConfiguration = Object.assign(baseComponentConfiguration, paymentMethodsExtraInfo[paymentMethod.type].configuration); + baseComponentConfiguration.productType = 'PayAndShip'; baseComponentConfiguration.checkoutMode = 'ProcessOrder'; - let url = new URL(location.href); - url.searchParams.delete(amazonSessionKey); - baseComponentConfiguration.returnUrl = url.href; - baseComponentConfiguration.onSubmit = async (state, amazonPayComponent) => { - try { - await self.handleOnSubmit(state.data, amazonPayComponent); - } catch (error) { - amazonPayComponent.handleDeclineFlow(); - } - }; + baseComponentConfiguration.showPayButton = true; + + // Redirect shoppers to the cart page if they cancel the payment on Amazon Pay hosted page. + baseComponentConfiguration.cancelUrl = urlBuilder.build('checkout/cart'); + // Redirect shoppers to the checkout if they complete the payment on Amazon Pay hosted page. + baseComponentConfiguration.returnUrl = urlBuilder.build('checkout/#payment'); if (formattedShippingAddress && formattedShippingAddress.telephone) { @@ -71,6 +76,7 @@ define( countryCode: formattedShippingAddress.country, phoneNumber: formattedShippingAddress.telephone }; + if (baseComponentConfiguration.addressDetails.countryCode === 'US') { baseComponentConfiguration.addressDetails.stateOrRegion = quote.shippingAddress().regionCode } @@ -88,21 +94,26 @@ define( countryCode: formattedBillingAddress.country, phoneNumber: formattedBillingAddress.telephone }; + if (baseComponentConfiguration.addressDetails.countryCode === 'US') { baseComponentConfiguration.addressDetails.stateOrRegion = quote.billingAddress().regionCode } } + return baseComponentConfiguration; }, mountPaymentMethodComponent: function (paymentMethod, configuration) { - let self = this; const containerId = '#' + paymentMethod.type + 'Container'; - let url = new URL(location.href); - //Handles the redirect back to checkout page with amazonSessionKey in url - if (url.searchParams.has(amazonSessionKey)) { + const currentUrl = new URL(location.href); + + /* + * If the first redirect is successful and URL contains `amazonCheckoutSessionId` parameter, + * don't mount the default component but mount the second component to submit the `/payments` request. + */ + if (currentUrl.searchParams.has(amazonSessionKey)) { let componentConfig = { - amazonCheckoutSessionId: url.searchParams.get(amazonSessionKey), + amazonCheckoutSessionId: currentUrl.searchParams.get(amazonSessionKey), showOrderButton: false, amount: { currency: configuration.amount.currency, @@ -110,23 +121,47 @@ define( }, showChangePaymentDetailsButton: false } - try { - const amazonPayComponent = adyenCheckout.mountPaymentMethodComponent( // This mountPaymentMethodCOmponent isn't up to date. - self.checkoutComponent, - 'amazonpay', - componentConfig, - containerId - ); - amazonPayComponent.submit(); - } catch (err) { - // The component does not exist yet - if ('test' === adyenConfiguration.getCheckoutEnvironment()) { - console.log(err); - } - } - } else{ + + this.amazonPayComponent = adyenCheckout.mountPaymentMethodComponent( + this.checkoutComponent, + 'amazonpay', + componentConfig, + containerId + ); + + // Triggers `onSubmit` event and `handleOnSubmit()` callback in adyen-pm-method.js handles it + this.amazonPayComponent.submit(); + } else { this._super(); } + }, + + /* + * Try to handle decline flow if Amazon Pay session allows in case of `/payments` call fails. + * If decline flow is available, shopper will be redirected to Amazon Pay hosted page again. + * If handle decline flow is not present, the component will throw `onError` event. + */ + handleOnFailure: function (response, component) { + this.amazonPayComponent.handleDeclineFlow(); + }, + + /* + * If `handleDeclineFlow()` can not be handled for any reason, `onError` will be thrown. + * In this case, remove `amazonCheckoutSessionId` from the URL and remount the payment component. + */ + handleOnError: function (error, component) { + this.remountAmazonPayComponent(); + }, + + /* + * Remove `amazonCheckoutSessionId` from the URL and remount the component. + */ + remountAmazonPayComponent: function () { + const checkoutPaymentUrl = "checkout/#payment"; + window.history.pushState({}, document.title, "/" + checkoutPaymentUrl); + + const paymentMethodsResponse = adyenPaymentService.getPaymentMethods(); + this.createCheckoutComponent(paymentMethodsResponse()); } }) } diff --git a/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js b/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js index e380a6413..604f5afe5 100755 --- a/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js @@ -87,7 +87,8 @@ define( paymentMethodsResponse, this.handleOnAdditionalDetails.bind(this), this.handleOnCancel.bind(this), - this.handleOnSubmit.bind(this) + this.handleOnSubmit.bind(this), + this.handleOnError.bind(this) ); if (!!this.checkoutComponent) { @@ -179,6 +180,21 @@ define( self.isPlaceOrderAllowed(true); }); }, + + handleOnError: function (error, component) { + /* + * Passing false as the response to hide the actual error message from the shopper for security. + * This will show a generic error message instead of the actual error message. + */ + this.handleOnFailure(error, component); + }, + + handleOnFailure: function(error, component) { + this.isPlaceOrderAllowed(true); + fullScreenLoader.stopLoader(); + errorProcessor.process(error, this.currentMessageContainer); + }, + renderCheckoutComponent: function() { let methodCode = this.getMethodCode(); @@ -374,7 +390,6 @@ define( try { const orderId = await placeOrderAction(data, self.currentMessageContainer); - self.afterPlaceOrder(); const responseJSON = await adyenPaymentService.getOrderPaymentStatus(orderId); self.validateActionOrPlaceOrder(responseJSON, orderId, component); } catch (response) { @@ -382,13 +397,6 @@ define( } }, - - handleOnFailure: function(response, component) { - this.isPlaceOrderAllowed(true); - fullScreenLoader.stopLoader(); - errorProcessor.process(response, this.currentMessageContainer); - }, - /** * This method is a workaround to close the modal in the right way and reconstruct the ActionModal. * This will solve issues when you cancel the 3DS2 challenge and retry the payment