Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECP-9352] Implement onError handler on Amazon Pay component #2768

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions etc/csp_whitelist.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<policy id="script-src">
<values>
<value id="adyen" type="host">*.adyen.com</value>
<value id="amazon-payments" type="host">*.payments-amazon.com</value>
</values>
</policy>
<policy id="frame-src">
Expand All @@ -14,16 +15,20 @@
<policy id="img-src">
<values>
<value id="adyen" type="host">*.adyen.com</value>
<value id="amazon-payments" type="host">*.payments-amazon.com</value>
<value id="amazon-media" type="host">*.media-amazon.com</value>
</values>
</policy>
<policy id="connect-src">
<values>
<value id="adyen" type="host">*.adyen.com</value>
<value id="amazon-eu" type="host">payments-eu.amazon.com</value>
</values>
</policy>
<policy id="form-action">
<values>
<value id="adyen" type="host">*.adyen.com</value>
<value id="amazon-payments-de" type="host">*.amazon.de</value>
</values>
</policy>
</policies>
Expand Down
11 changes: 9 additions & 2 deletions 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) {
buildCheckoutComponent: function (
paymentMethodsResponse,
handleOnAdditionalDetails,
handleOnCancel = undefined,
handleOnSubmit = undefined,
handleOnError = undefined
) {
if (!!paymentMethodsResponse.paymentMethodsResponse) {
return AdyenCheckout({
locale: adyenConfiguration.getLocale(),
Expand All @@ -28,7 +34,8 @@ define(
paymentMethodsResponse: paymentMethodsResponse.paymentMethodsResponse,
onAdditionalDetails: handleOnAdditionalDetails,
onCancel: handleOnCancel,
onSubmit: handleOnSubmit
onSubmit: handleOnSubmit,
onError: handleOnError
}
);
} else {
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 @@ -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) {
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -374,21 +390,13 @@ define(

try {
const orderId = await placeOrderAction(data, self.currentMessageContainer);
self.afterPlaceOrder();
const responseJSON = await adyenPaymentService.getOrderPaymentStatus(orderId);
self.validateActionOrPlaceOrder(responseJSON, orderId, component);
} catch (response) {
self.handleOnFailure(response, 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
Loading