Skip to content

Commit

Permalink
[ECP-9352] Implement onError handler on Amazon Pay component (#2768)
Browse files Browse the repository at this point in the history
* [ECP-9352] Update csp allowed domains

* [ECP-9352] Update returnUrl and cancelUrl

* [ECP-9352] Add Amazon Pay assets to CSP allow list

* [ECP-9352] Implement handleOnError and handleOnFailure callbacks

* [ECP-9352] Update error handling

---------

Co-authored-by: Can Demiralp <[email protected]>
  • Loading branch information
candemiralp and Can Demiralp authored Nov 5, 2024
1 parent 439c731 commit c3fc3a5
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 52 deletions.
2 changes: 1 addition & 1 deletion etc/csp_whitelist.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<value id="adyen" type="host">*.adyen.com</value>
<value id="googlepay" type="host">pay.google.com</value>
<value id="amazonpay-eu" type="host">payments-eu.amazon.com</value>
<value id="amazonpay-de" type="host">payments.amazon.de</value>
<value id="amazonpay-de" type="host">*.amazon.de</value>
</values>
</policy>
</policies>
Expand Down
8 changes: 7 additions & 1 deletion view/frontend/web/js/model/adyen-checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ define(
) {
'use strict';
return {
buildCheckoutComponent: function (paymentMethodsResponse, handleOnAdditionalDetails, handleOnCancel = undefined, handleOnSubmit = undefined, handleOnError = undefined) {
buildCheckoutComponent: function (
paymentMethodsResponse,
handleOnAdditionalDetails,
handleOnCancel = undefined,
handleOnSubmit = undefined,
handleOnError = undefined
) {
if (!!paymentMethodsResponse.paymentMethodsResponse) {
return AdyenCheckout({
locale: adyenConfiguration.getLocale(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,57 @@ 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());
}

if (!!quote.billingAddress()) {
formattedBillingAddress = self.getFormattedAddress(quote.billingAddress());
}
baseComponentConfiguration.showPayButton = true;

baseComponentConfiguration.onClick = function(resolve,reject) {
if (self.validate()) {
resolve();
} else {
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) {
Expand All @@ -71,6 +76,7 @@ define(
countryCode: formattedShippingAddress.country,
phoneNumber: formattedShippingAddress.telephone
};

if (baseComponentConfiguration.addressDetails.countryCode === 'US') {
baseComponentConfiguration.addressDetails.stateOrRegion = quote.shippingAddress().regionCode
}
Expand All @@ -88,45 +94,74 @@ 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,
value: configuration.amount.value
},
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());
}
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,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();

Expand Down Expand Up @@ -382,19 +397,6 @@ define(
}
},

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(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
Expand Down

0 comments on commit c3fc3a5

Please sign in to comment.