From fe219b3be5672415885e5c1e257bcdab36d0b7a2 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 15 Apr 2024 17:10:26 +0200 Subject: [PATCH 1/3] [ECP-8400] Support virtual products on express payments (#78) * [ECP-8400] Implement virtualQuoteModel to obtain quote type * [ECP-8400] Fix pdp virtual quote condition * [ECP-8400] Fix placing order with virtual product * [ECP-8400] Handle payment status * [ECP-8400] Support virtual products on Apple Pay express * [ECP-8400] Update the related callbacks * [ECP-8400] Fix virtualQuote model * [ECP-8400] Fix virtualQuote model * [ECP-8400] Fix virtualQuote model --------- Co-authored-by: Can Demiralp --- Api/Data/ExpressDataInterface.php | 18 + Model/ExpressData.php | 25 +- Model/ExpressDataBuilder.php | 1 + .../web/js/actions/getPaymentStatus.js | 39 ++ .../web/js/actions/setBillingAddress.js | 13 + view/frontend/web/js/applepay/button.js | 413 +++++++++--------- view/frontend/web/js/googlepay/button.js | 123 ++++-- view/frontend/web/js/model/virtualQuote.js | 39 ++ 8 files changed, 422 insertions(+), 249 deletions(-) create mode 100644 view/frontend/web/js/actions/getPaymentStatus.js create mode 100644 view/frontend/web/js/actions/setBillingAddress.js create mode 100644 view/frontend/web/js/model/virtualQuote.js diff --git a/Api/Data/ExpressDataInterface.php b/Api/Data/ExpressDataInterface.php index 98f212c..32273aa 100644 --- a/Api/Data/ExpressDataInterface.php +++ b/Api/Data/ExpressDataInterface.php @@ -8,6 +8,7 @@ interface ExpressDataInterface public const MASKED_QUOTE_ID = 'masked_quote_id'; public const ADYEN_PAYMENT_METHODS = 'adyen_payment_methods'; public const TOTALS = 'totals'; + public const IS_VIRTUAL_QUOTE = 'is_virtual_quote'; /** * Get Masked Quote ID @@ -59,4 +60,21 @@ public function getTotals(): ?\Magento\Quote\Api\Data\TotalsInterface; public function setTotals( \Magento\Quote\Api\Data\TotalsInterface $totals ): void; + + /** + * Is quote virtual + * + * @return bool|null + */ + public function getIsVirtualQuote(): ?bool; + + /** + * Set is quote virtual + * + * @param bool $isVirtual + * @return void + */ + public function setIsVirtualQuote( + bool $isVirtual + ): void; } diff --git a/Model/ExpressData.php b/Model/ExpressData.php index a5f6298..904c5cb 100644 --- a/Model/ExpressData.php +++ b/Model/ExpressData.php @@ -7,7 +7,6 @@ use Adyen\ExpressCheckout\Api\Data\ExpressDataInterface; use Magento\Framework\DataObject; use Magento\Quote\Api\Data\TotalsInterface; -use Magento\Quote\Model\Quote\Address\Total; class ExpressData extends DataObject implements ExpressDataInterface { @@ -94,4 +93,28 @@ public function setTotals(TotalsInterface $totals): void $totals ); } + + /** + * @return bool|null + */ + public function getIsVirtualQuote(): ?bool + { + $isVirtual = $this->getData(self::IS_VIRTUAL_QUOTE); + + return $isVirtual ? + (bool) $isVirtual : + null; + } + + /** + * @param bool $isVirtual + * @return void + */ + public function setIsVirtualQuote(bool $isVirtual): void + { + $this->setData( + self::IS_VIRTUAL_QUOTE, + $isVirtual + ); + } } diff --git a/Model/ExpressDataBuilder.php b/Model/ExpressDataBuilder.php index 855e2f2..d3291d7 100644 --- a/Model/ExpressDataBuilder.php +++ b/Model/ExpressDataBuilder.php @@ -175,6 +175,7 @@ public function execute( $expressData->setAdyenPaymentMethods($adyenPaymentMethods); $maskedQuoteId = $this->getMaskedQuoteId($quote); $expressData->setMaskedQuoteId($maskedQuoteId); + $expressData->setIsVirtualQuote($quote->isVirtual()); $cartTotals = $this->cartTotalRepository->get( $quote->getId() ); diff --git a/view/frontend/web/js/actions/getPaymentStatus.js b/view/frontend/web/js/actions/getPaymentStatus.js new file mode 100644 index 0000000..e75ea32 --- /dev/null +++ b/view/frontend/web/js/actions/getPaymentStatus.js @@ -0,0 +1,39 @@ +define([ + 'mage/storage', + 'Adyen_ExpressCheckout/js/helpers/getIsLoggedIn', + 'Adyen_ExpressCheckout/js/model/maskedId', + 'Adyen_ExpressCheckout/js/model/config', + 'Adyen_ExpressCheckout/js/helpers/getMaskedIdFromCart' +], function ( + storage, + isLoggedIn, + maskedIdModel, + configModel, + getMaskedIdFromCart +) { + 'use strict'; + + return function (orderId, isProductView) { + + return new Promise(function (resolve, reject) { + const maskedId = isProductView + ? maskedIdModel().getMaskedId() + : getMaskedIdFromCart(); + + const config = configModel().getConfig(); + + let url = isLoggedIn() + ? 'rest/' + config.storeCode + '/V1/adyen/orders/carts/mine/payment-status' + : 'rest/' + config.storeCode + '/V1/adyen/orders/guest-carts/' + maskedId + '/payment-status'; + + let payload = { + orderId: orderId + } + + storage.post( + url, + JSON.stringify(payload) + ).done(resolve).fail(reject); + }); + }; +}); diff --git a/view/frontend/web/js/actions/setBillingAddress.js b/view/frontend/web/js/actions/setBillingAddress.js new file mode 100644 index 0000000..6bcb0f6 --- /dev/null +++ b/view/frontend/web/js/actions/setBillingAddress.js @@ -0,0 +1,13 @@ +define([ + 'mage/storage', + 'Adyen_ExpressCheckout/js/helpers/getApiUrl' +], function (storage, getApiUrl) { + 'use strict'; + + return function (payload, isProductView) { + return storage.post( + getApiUrl('billing-address', isProductView), + JSON.stringify(payload) + ); + }; +}); diff --git a/view/frontend/web/js/applepay/button.js b/view/frontend/web/js/applepay/button.js index f75d394..2f37769 100644 --- a/view/frontend/web/js/applepay/button.js +++ b/view/frontend/web/js/applepay/button.js @@ -10,6 +10,7 @@ define([ 'Adyen_ExpressCheckout/js/actions/getShippingMethods', 'Adyen_ExpressCheckout/js/actions/getExpressMethods', 'Adyen_ExpressCheckout/js/actions/setShippingInformation', + 'Adyen_ExpressCheckout/js/actions/setBillingAddress', 'Adyen_ExpressCheckout/js/actions/setTotalsInfo', 'Adyen_ExpressCheckout/js/helpers/formatAmount', 'Adyen_ExpressCheckout/js/helpers/getApplePayStyles', @@ -27,7 +28,8 @@ define([ 'Adyen_ExpressCheckout/js/model/config', 'Adyen_ExpressCheckout/js/model/countries', 'Adyen_ExpressCheckout/js/model/totals', - 'Adyen_ExpressCheckout/js/model/currency' + 'Adyen_ExpressCheckout/js/model/currency', + 'Adyen_ExpressCheckout/js/model/virtualQuote' ], function ( Component, @@ -41,6 +43,7 @@ define([ getShippingMethods, getExpressMethods, setShippingInformation, + setBillingAddress, setTotalsInfo, formatAmount, getApplePayStyles, @@ -58,7 +61,8 @@ define([ configModel, countriesModel, totalsModel, - currencyModel + currencyModel, + virtualQuoteModel ) { 'use strict'; @@ -83,6 +87,8 @@ define([ const response = await getExpressMethods().getRequest(element); const cart = customerData.get('cart'); + virtualQuoteModel().setIsVirtual(true, response); + cart.subscribe(function () { this.reloadApplePayButton(element); }.bind(this)); @@ -117,6 +123,8 @@ define([ this.initialiseApplePayComponent(applePaymentMethod, element); } else { let applePaymentMethod = await getPaymentMethod('applepay', this.isProductView); + virtualQuoteModel().setIsVirtual(false); + if (!applePaymentMethod) { const cart = customerData.get('cart'); cart.subscribe(function () { @@ -181,8 +189,11 @@ define([ 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.unmountApplePay(); @@ -199,7 +210,9 @@ define([ const config = configModel().getConfig(); const countryCode = config.countryCode === 'UK' ? 'GB' : config.countryCode; const pdpForm = getPdpForm(element); + const isVirtual = virtualQuoteModel().getIsVirtual(); let currency; + let applepayBaseConfiguration; if (this.isProductView) { currency = currencyModel().getCurrency(); @@ -210,7 +223,7 @@ define([ currency = paymentMethodExtraDetails.configuration.amount.currency; } - return { + applepayBaseConfiguration = { countryCode: countryCode, currencyCode: currency, totalPriceLabel: this.getMerchantName(), @@ -231,13 +244,18 @@ define([ requiredBillingContactFields: ['postalAddress', 'name'], shippingMethods: [], onAuthorized: this.startPlaceOrder.bind(this), - onShippingContactSelected: this.onShippingContactSelect.bind(this), - onShippingMethodSelected: this.onShippingMethodSelect.bind(this), onClick: function (resolve, reject) {validatePdpForm(resolve, reject, pdpForm);}, onSubmit: function () {}, onError: () => cancelCart(this.isProductView), ...applePayStyles }; + + if (!isVirtual) { + applepayBaseConfiguration.onShippingContactSelected = this.onShippingContactSelect.bind(this); + applepayBaseConfiguration.onShippingMethodSelected = this.onShippingMethodSelect.bind(this); + } + + return applepayBaseConfiguration; }, onShippingContactSelect: function (resolve, reject, event) { @@ -258,102 +276,70 @@ define([ self.shippingAddress = payload.address; - activateCart(this.isProductView) - .then(() => getShippingMethods(payload, this.isProductView)) - .then((result) => { - // Stop if no shipping methods. - if (result.length === 0) { - reject($t('There are no shipping methods available for you right now. Please try again or use an alternative payment method.')); - } - let shippingMethods = []; - - self.shippingMethods = {}; - // Format shipping methods array. - for (let i = 0; i < result.length; i++) { - if (typeof result[i].method_code !== 'string') { - continue; - } - let method = { - identifier: result[i].method_code, - label: result[i].method_title, - detail: result[i].carrier_title ? result[i].carrier_title : '', - amount: parseFloat(result[i].amount).toFixed(2) - }; - // Add method object to array. + getShippingMethods(payload, this.isProductView).then((result) => { + // Stop if no shipping methods. + if (result.length === 0) { + reject($t('There are no shipping methods available for you right now. Please try again or use an alternative payment method.')); + } + let shippingMethods = []; - shippingMethods.push(method); - self.shippingMethods[result[i].method_code] = result[i]; - if (!self.shippingMethod) { - self.shippingMethod = result[i].method_code; - } + self.shippingMethods = {}; + // Format shipping methods array. + for (let i = 0; i < result.length; i++) { + if (typeof result[i].method_code !== 'string') { + continue; } - - let address = { - 'countryId': self.shippingAddress.country_id, - 'region': self.shippingAddress.region, - 'regionId': getRegionId(self.shippingAddress.country_id, self.shippingAddress.region), - 'postcode': self.shippingAddress.postcode + let method = { + identifier: result[i].method_code, + label: result[i].method_title, + detail: result[i].carrier_title ? result[i].carrier_title : '', + amount: parseFloat(result[i].amount).toFixed(2) }; + // Add method object to array. + shippingMethods.push(method); + self.shippingMethods[result[i].method_code] = result[i]; + if (!self.shippingMethod) { + self.shippingMethod = result[i].method_code; + } + } - // Create payload to get totals - let totalsPayload = { - 'addressInformation': { - 'address': address, - 'shipping_method_code': self.shippingMethods[shippingMethods[0].identifier].method_code, - 'shipping_carrier_code': self.shippingMethods[shippingMethods[0].identifier].carrier_code - } - }; + let address = { + 'countryId': self.shippingAddress.country_id, + 'region': self.shippingAddress.region, + 'regionId': getRegionId(self.shippingAddress.country_id, self.shippingAddress.region), + 'postcode': self.shippingAddress.postcode + }; - // Create payload to update quote - let shippingInformationPayload = { - 'addressInformation': { - ...totalsPayload.addressInformation, - 'shipping_address': address, - 'billing_address': address - } - }; + // Create payload to get totals + let totalsPayload = { + 'addressInformation': { + 'address': address, + 'shipping_method_code': self.shippingMethods[shippingMethods[0].identifier].method_code, + 'shipping_carrier_code': self.shippingMethods[shippingMethods[0].identifier].carrier_code + } + }; - delete shippingInformationPayload.addressInformation.address; - setShippingInformation(shippingInformationPayload, this.isProductView); + // Create payload to update quote + let shippingInformationPayload = { + 'addressInformation': { + 'shipping_address': address, + 'shipping_method_code': self.shippingMethods[shippingMethods[0].identifier].method_code, + 'shipping_carrier_code': self.shippingMethods[shippingMethods[0].identifier].carrier_code + } + }; - setTotalsInfo(totalsPayload, this.isProductView) + setShippingInformation(shippingInformationPayload, self.isProductView).then(() => { + setTotalsInfo(totalsPayload, self.isProductView) .done((response) => { - let applePayShippingContactUpdate = {}; - - applePayShippingContactUpdate.newShippingMethods = shippingMethods; - applePayShippingContactUpdate.newTotal = { - label: this.getMerchantName(), - amount: (response.grand_total).toString() - }; - applePayShippingContactUpdate.newLineItems = [ - { - type: 'final', - label: $t('Subtotal'), - amount: response.subtotal.toString() - }, - { - type: 'final', - label: $t('Shipping'), - amount: shippingMethods[0].amount.toString() - } - ]; - - if (response.tax_amount > 0) { - applePayShippingContactUpdate.newLineItems.push({ - type: 'final', - label: $t('Tax'), - amount: response.tax_amount.toString() - }) - } - - resolve(applePayShippingContactUpdate); - // Pass shipping methods back - }).fail((e) => { + self.afterSetTotalsInfo(response, shippingMethods[0], self.isProductView, resolve); + }) + .fail((e) => { console.error('Adyen ApplePay: Unable to get totals', e); reject($t('We\'re unable to fetch the cart totals for you. Please try an alternative payment method.')); - }); - }).catch(reject); + }); + }); + }); }, onShippingMethodSelect: function (resolve, reject, event) { @@ -377,136 +363,161 @@ define([ let shippingInformationPayload = { 'addressInformation': { - ...totalsPayload.addressInformation, - 'shipping_address': address, - 'billing_address': address + 'shipping_method_code': self.shippingMethods[shippingMethod.identifier].method_code, + 'shipping_carrier_code': self.shippingMethods[shippingMethod.identifier].carrier_code, + 'shipping_address': address } }; - delete shippingInformationPayload.addressInformation.address; - setShippingInformation(shippingInformationPayload, this.isProductView); + setShippingInformation(shippingInformationPayload, this.isProductView).then(() => { + setTotalsInfo(totalsPayload, self.isProductView) + .done((response) => { + self.afterSetTotalsInfo(response, shippingMethod, self.isProductView, resolve); + }).fail((e) => { + console.error('Adyen ApplePay: Unable to get totals', e); + reject($t('We\'re unable to fetch the cart totals for you. Please try an alternative payment method.')); + }); + }); + }, - setTotalsInfo(totalsPayload, this.isProductView) - .done((response) => { - let applePayShippingMethodUpdate = {}; + afterSetTotalsInfo: function (response, shippingMethod, isPdp, resolve) { + let applePayShippingMethodUpdate = {}; - applePayShippingMethodUpdate.newTotal = { - type: 'final', - label: this.getMerchantName(), - amount: (response.grand_total).toString() - }; - applePayShippingMethodUpdate.newLineItems = [ - { - type: 'final', - label: $t('Subtotal'), - amount: response.subtotal.toString() - }, - { - type: 'final', - label: $t('Shipping'), - amount: shippingMethod.amount.toString() - } - ]; - - if (response.tax_amount > 0) { - applePayShippingMethodUpdate.newLineItems.push({ - type: 'final', - label: $t('Tax'), - amount: response.tax_amount.toString() - }) - } + applePayShippingMethodUpdate.newTotal = { + type: 'final', + label: this.getMerchantName(), + amount: (response.grand_total).toString() + }; + applePayShippingMethodUpdate.newLineItems = [ + { + type: 'final', + label: $t('Subtotal'), + amount: response.subtotal.toString() + }, + { + type: 'final', + label: $t('Shipping'), + amount: shippingMethod.amount.toString() + } + ]; + + if (response.tax_amount > 0) { + applePayShippingMethodUpdate.newLineItems.push({ + type: 'final', + label: $t('Tax'), + amount: response.tax_amount.toString() + }) + } - self.shippingMethod = shippingMethod.identifier; - resolve(applePayShippingMethodUpdate); - }).fail((e) => { - console.error('Adyen ApplePay: Unable to get totals', e); - reject($t('We\'re unable to fetch the cart totals for you. Please try an alternative payment method.')); - }); + this.shippingMethod = shippingMethod.identifier; + resolve(applePayShippingMethodUpdate); }, /** * Place the order */ startPlaceOrder: function (resolve, reject, event) { - let self = this; - let shippingContact = event.payment.shippingContact, - billingContact = event.payment.billingContact, - payload = { - 'addressInformation': { - 'shipping_address': { - 'email': shippingContact.emailAddress, - 'telephone': shippingContact.phoneNumber, - 'firstname': shippingContact.givenName, - 'lastname': shippingContact.familyName, - 'street': shippingContact.addressLines, - 'city': shippingContact.locality, - 'region': shippingContact.administrativeArea, - 'region_id': getRegionId( - shippingContact.countryCode.toUpperCase(), - shippingContact.administrativeArea - ), - 'region_code': null, - 'country_id': shippingContact.countryCode.toUpperCase(), - 'postcode': shippingContact.postalCode, - 'same_as_billing': 0, - 'customer_address_id': 0, - 'save_in_address_book': 0 - }, - 'billing_address': { - 'email': shippingContact.emailAddress, - 'telephone': shippingContact.phoneNumber, - 'firstname': billingContact.givenName, - 'lastname': billingContact.familyName, - 'street': billingContact.addressLines, - 'city': billingContact.locality, - 'region': billingContact.administrativeArea, - 'region_id': getRegionId(billingContact.countryCode.toUpperCase(), billingContact.administrativeArea), - 'region_code': null, - 'country_id': billingContact.countryCode.toUpperCase(), - 'postcode': billingContact.postalCode, - 'same_as_billing': 0, - 'customer_address_id': 0, - 'save_in_address_book': 0 - }, - 'shipping_method_code': self.shippingMethods[self.shippingMethod].method_code, - 'shipping_carrier_code': self.shippingMethods[self.shippingMethod].carrier_code, - 'extension_attributes': getExtensionAttributes(event.payment) - } - }; + const isVirtual = virtualQuoteModel().getIsVirtual(); + let self = this; let componentData = self.applePayComponent.data; - setShippingInformation(payload, this.isProductView).done(function () { - // Submit payment information - const postData = { - email: shippingContact.emailAddress, - paymentMethod: { - method: 'adyen_applepay', - additional_data: { - brand_code: 'applepay', - stateData: JSON.stringify(componentData) - } - } - }; + let shippingContact = event.payment.shippingContact; + let billingContact = event.payment.billingContact; - if (window.checkout && window.checkout.agreementIds) { - postData.paymentMethod.extension_attributes = { - agreement_ids: window.checkout.agreementIds - }; + let billingAddressPayload = { + address: { + 'email': shippingContact.emailAddress, + 'telephone': shippingContact.phoneNumber, + 'firstname': billingContact.givenName, + 'lastname': billingContact.familyName, + 'street': billingContact.addressLines, + 'city': billingContact.locality, + 'region': billingContact.administrativeArea, + 'region_id': getRegionId(billingContact.countryCode.toUpperCase(), billingContact.administrativeArea), + 'region_code': null, + 'country_id': billingContact.countryCode.toUpperCase(), + 'postcode': billingContact.postalCode, + 'same_as_billing': 0, + 'customer_address_id': 0, + 'save_in_address_book': 0 + }, + 'useForShipping': false + }; + + const postData = { + email: shippingContact.emailAddress, + paymentMethod: { + method: 'adyen_applepay', + additional_data: { + brand_code: 'applepay', + stateData: JSON.stringify(componentData) + } } + }; - createPayment(JSON.stringify(postData), this.isProductView) - .done(function () { - redirectToSuccess(); - resolve(window.ApplePaySession.STATUS_SUCCESS); - }).fail(function (r) { - reject(window.ApplePaySession.STATUS_FAILURE); - console.error('Adyen ApplePay Unable to take payment', r); - }); + if (window.checkout && window.checkout.agreementIds) { + postData.paymentMethod.extension_attributes = { + agreement_ids: window.checkout.agreementIds + }; + } - }.bind(this)).fail(function (e) { - console.error('Adyen ApplePay Unable to set shipping information', e); - reject(window.ApplePaySession.STATUS_INVALID_BILLING_POSTAL_ADDRESS); + activateCart(this.isProductView).then(() => { + setBillingAddress(billingAddressPayload, self.isProductView).done(() => { + if (!isVirtual) { + let shippingInformationPayload = { + 'addressInformation': { + 'shipping_address': { + 'email': shippingContact.emailAddress, + 'telephone': shippingContact.phoneNumber, + 'firstname': shippingContact.givenName, + 'lastname': shippingContact.familyName, + 'street': shippingContact.addressLines, + 'city': shippingContact.locality, + 'region': shippingContact.administrativeArea, + 'region_id': getRegionId( + shippingContact.countryCode.toUpperCase(), + shippingContact.administrativeArea + ), + 'region_code': null, + 'country_id': shippingContact.countryCode.toUpperCase(), + 'postcode': shippingContact.postalCode, + 'same_as_billing': 0, + 'customer_address_id': 0, + 'save_in_address_book': 0 + }, + 'shipping_method_code': self.shippingMethods[self.shippingMethod].method_code, + 'shipping_carrier_code': self.shippingMethods[self.shippingMethod].carrier_code, + 'extension_attributes': getExtensionAttributes(event.payment) + } + }; + + setShippingInformation(shippingInformationPayload, self.isProductView).done(function () { + // Submit payment information + createPayment(JSON.stringify(postData), self.isProductView) + .done(function () { + redirectToSuccess(); + resolve(window.ApplePaySession.STATUS_SUCCESS); + }).fail(function (r) { + reject(window.ApplePaySession.STATUS_FAILURE); + console.error('Adyen ApplePay Unable to take payment', r); + }); + + }.bind(self)).fail(function (e) { + console.error('Adyen ApplePay Unable to set shipping information', e); + reject(window.ApplePaySession.STATUS_INVALID_BILLING_POSTAL_ADDRESS); + }); + } else { + createPayment(JSON.stringify(postData), self.isProductView) + .done(function () { + redirectToSuccess(); + resolve(window.ApplePaySession.STATUS_SUCCESS); + }).fail(function (r) { + reject(window.ApplePaySession.STATUS_FAILURE); + console.error('Adyen ApplePay Unable to take payment', r); + }); + } + }); }); }, diff --git a/view/frontend/web/js/googlepay/button.js b/view/frontend/web/js/googlepay/button.js index 321295e..9aea73e 100644 --- a/view/frontend/web/js/googlepay/button.js +++ b/view/frontend/web/js/googlepay/button.js @@ -14,7 +14,9 @@ define([ 'Adyen_ExpressCheckout/js/actions/createPayment', 'Adyen_ExpressCheckout/js/actions/getShippingMethods', 'Adyen_ExpressCheckout/js/actions/getExpressMethods', + 'Adyen_ExpressCheckout/js/actions/getPaymentStatus', 'Adyen_ExpressCheckout/js/actions/setShippingInformation', + 'Adyen_ExpressCheckout/js/actions/setBillingAddress', 'Adyen_ExpressCheckout/js/actions/setTotalsInfo', 'Adyen_ExpressCheckout/js/helpers/formatAmount', 'Adyen_ExpressCheckout/js/helpers/formatCurrency', @@ -35,7 +37,7 @@ define([ 'Adyen_ExpressCheckout/js/model/countries', 'Adyen_ExpressCheckout/js/model/totals', 'Adyen_ExpressCheckout/js/model/currency', - 'mage/cookies', + 'Adyen_ExpressCheckout/js/model/virtualQuote' ], function ( $, @@ -53,7 +55,9 @@ define([ createPayment, getShippingMethods, getExpressMethods, + getPaymentStatus, setShippingInformation, + setBillingAddress, setTotalsInfo, formatAmount, formatCurrency, @@ -73,7 +77,8 @@ define([ configModel, countriesModel, totalsModel, - currencyModel + currencyModel, + virtualQuoteModel ) { 'use strict'; @@ -109,6 +114,7 @@ define([ this.initializeOnPDP(config, element); } else { let googlePaymentMethod = await getPaymentMethod('googlepay', this.isProductView); + virtualQuoteModel().setIsVirtual(false); if (!googlePaymentMethod) { const cart = customerData.get('cart'); @@ -128,6 +134,7 @@ define([ initializeOnPDP: async function (config, element) { const response = await getExpressMethods().getRequest(element); const cart = customerData.get('cart'); + virtualQuoteModel().setIsVirtual(true, response); cart.subscribe(function () { this.reloadGooglePayButton(element); @@ -201,8 +208,11 @@ define([ 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.unmountGooglePay(); @@ -218,6 +228,7 @@ define([ const googlePayStyles = getGooglePayStyles(); const config = configModel().getConfig(); const pdpForm = getPdpForm(element); + const isVirtual = virtualQuoteModel().getIsVirtual(); let currency; if (this.isProductView) { @@ -235,8 +246,8 @@ define([ environment: config.checkoutenv.toUpperCase(), showButton: true, emailRequired: true, - shippingAddressRequired: true, - shippingOptionRequired: true, + shippingAddressRequired: !isVirtual, + shippingOptionRequired: !isVirtual, shippingAddressParameters: { phoneNumberRequired: true }, @@ -245,7 +256,7 @@ define([ format: 'FULL', phoneNumberRequired: true }, - callbackIntents: ['SHIPPING_ADDRESS', 'SHIPPING_OPTION'], + callbackIntents: !isVirtual ? ['SHIPPING_ADDRESS', 'SHIPPING_OPTION'] : ['OFFER'], transactionInfo: { totalPriceStatus: 'ESTIMATED', totalPrice: this.isProductView @@ -254,7 +265,7 @@ define([ currencyCode: currency }, paymentDataCallbacks: { - onPaymentDataChanged: this.onPaymentDataChanged.bind(this) + onPaymentDataChanged: this.onPaymentDataChanged.bind(this) }, allowedPaymentMethods: ['CARD'], phoneNumberRequired: true, @@ -273,6 +284,10 @@ define([ onPaymentDataChanged: function (data) { return new Promise((resolve, reject) => { + if (virtualQuoteModel().getIsVirtual()) { + resolve(); + } + const payload = { address: { country_id: data.shippingAddress.countryCode, @@ -383,45 +398,56 @@ define([ startPlaceOrder: function (paymentData) { let self = this; - let componentData = self.googlePayComponent.data; - - this.setShippingInformation(paymentData) - .done(function () { - const payload = { - email: paymentData.email, - shippingAddress: this.mapAddress(paymentData.shippingAddress), - billingAddress: this.mapAddress(paymentData.paymentMethodData.info.billingAddress), - paymentMethod: { - method: 'adyen_googlepay', - additional_data: { - brand_code: self.googlePayComponent.props.type, - stateData: JSON.stringify(componentData) - }, - extension_attributes: getExtensionAttributes(paymentData) - } - }; + const isVirtual = virtualQuoteModel().getIsVirtual(); - if (window.checkout && window.checkout.agreementIds) { - payload.paymentMethod.extension_attributes = { - agreement_ids: window.checkout.agreementIds - }; + activateCart(this.isProductView).then(function () { + self.setBillingAddress(paymentData).done(function () { + if (!isVirtual) { + self.setShippingInformation(paymentData).done(function () { + self.placeOrder(paymentData); + }); + } else { + self.placeOrder(paymentData); } + }); + }); + }, - createPayment(JSON.stringify(payload), this.isProductView) - .done( function (orderId) { - if (!!orderId) { - self.orderId = orderId; - let quoteId = self.isProductView ? maskedIdModel().getMaskedId() : getMaskedIdFromCart(); - adyenPaymentService.getOrderPaymentStatus(orderId, quoteId). - done(function (responseJSON) { - self.handleAdyenResult(responseJSON, orderId); - }) - } - }) - .fail(function (e) { - console.error('Adyen GooglePay Unable to take payment', e); + placeOrder: function (paymentData) { + let self = this; + let componentData = this.googlePayComponent.data; + + const payload = { + email: paymentData.email, + paymentMethod: { + method: 'adyen_googlepay', + additional_data: { + brand_code: this.googlePayComponent.props.type, + stateData: JSON.stringify(componentData) + }, + extension_attributes: getExtensionAttributes(paymentData) + } + }; + + if (window.checkout && window.checkout.agreementIds) { + payload.paymentMethod.extension_attributes = { + agreement_ids: window.checkout.agreementIds + }; + } + + createPayment(JSON.stringify(payload), this.isProductView) + .done(function (orderId) { + if (!!orderId) { + self.orderId = orderId; + + getPaymentStatus(orderId, self.isProductView).then(function (responseJSON) { + self.handleAdyenResult(responseJSON, orderId); }); - }.bind(this)); + } + }) + .fail(function (e) { + console.error('Adyen GooglePay Unable to take payment', e); + }); }, setShippingInformation: function (paymentData) { @@ -436,12 +462,6 @@ define([ 'customer_address_id': 0, 'save_in_address_book': 0 }, - 'billing_address': { - ...this.mapAddress(paymentData.paymentMethodData.info.billingAddress), - 'same_as_billing': 0, - 'customer_address_id': 0, - 'save_in_address_book': 0 - }, 'shipping_method_code': shippingMethod.method_code, 'shipping_carrier_code': shippingMethod.carrier_code, 'extension_attributes': getExtensionAttributes(paymentData) @@ -451,6 +471,15 @@ define([ return setShippingInformation(payload, this.isProductView); }, + setBillingAddress: function (paymentData) { + let payload = { + 'address': this.mapAddress(paymentData.paymentMethodData.info.billingAddress), + 'useForShipping': false + }; + + return setBillingAddress(payload, this.isProductView); + }, + handleOnAdditionalDetails: function (result) { const self = this; let request = result.data; diff --git a/view/frontend/web/js/model/virtualQuote.js b/view/frontend/web/js/model/virtualQuote.js new file mode 100644 index 0000000..f4e5d57 --- /dev/null +++ b/view/frontend/web/js/model/virtualQuote.js @@ -0,0 +1,39 @@ +define([ + 'uiComponent', + 'Magento_Customer/js/customer-data', + 'ko' +], function ( + Component, + customerData, + ko +) { + 'use strict'; + return Component.extend({ + defaults: { + isVirtual: ko.observable(0).extend({notify: 'always'}) + }, + + getIsVirtual: function () { + return this.isVirtual(); + }, + + setIsVirtual: function (isPdp, initExpressResponse = null) { + let isVirtual = false; + + if (isPdp && initExpressResponse['is_virtual_quote']) { + isVirtual = true; + } else if (!isPdp) { + const cart = customerData.get('cart'); + isVirtual = true; + + cart().items.forEach((item) => { + if (!["virtual", "downloadable"].includes(item.product_type)) { + isVirtual = false; + } + }); + } + + return this.isVirtual(isVirtual); + } + }); +}); From a1638cfe387db05bd358654a4ce4fb2fe92e3c25 Mon Sep 17 00:00:00 2001 From: Dimitri BOUTEILLE <34821762+dimitriBouteille@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:21:36 +0200 Subject: [PATCH 2/3] Add onAvailable event for Apple Pay (#82) Co-authored-by: Dimitri BOUTEILLE Co-authored-by: Can Demiralp --- view/frontend/web/js/applepay/button.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/view/frontend/web/js/applepay/button.js b/view/frontend/web/js/applepay/button.js index 2f37769..08a77e8 100644 --- a/view/frontend/web/js/applepay/button.js +++ b/view/frontend/web/js/applepay/button.js @@ -166,17 +166,28 @@ define([ this.applePayComponent .isAvailable() .then(() => { - element.style.display = 'block'; - this.applePayComponent.mount(element); - }).catch((e) => { + this.onAvailable(element); + }) + .catch((e) => { this.onNotAvailable(e); }); }, + /** + * @param {*} error + */ onNotAvailable: function (error) { console.log('Apple pay is unavailable.', error); }, + /** + * @param {HTMLElement} element + */ + onAvailable: function (element) { + element.style.display = 'block'; + this.applePayComponent.mount(element); + }, + unmountApplePay: function () { if (this.applePayComponent) { this.applePayComponent.unmount(); From 8e3c0c2a9e33e1b600a41ef803384635e8a901a3 Mon Sep 17 00:00:00 2001 From: RokPopov Date: Mon, 22 Apr 2024 11:37:56 +0200 Subject: [PATCH 3/3] Version bump 2.1.0 --- composer.json | 2 +- etc/module.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index df1a10b..77a85e8 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.0.2", + "version": "2.1.0", "license": "MIT", "repositories": [ { diff --git a/etc/module.xml b/etc/module.xml index ae72c01..b5ae567 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -11,7 +11,7 @@ */ --> - +