From 128bfab2e57b714cf80619fe418b35c6ff3d6fdc Mon Sep 17 00:00:00 2001 From: Aashish Gurung <101558497+aashishgurung@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:36:57 +0700 Subject: [PATCH] Revert "Revert "[MIT-1911] Whitelabel installment" (#487)" (#491) This reverts commit d936ae8779ecb6b44f56d908d4f4a1d26353e408. --- Gateway/Request/APMBuilder.php | 21 +- Gateway/Request/PaymentDataBuilder.php | 12 ++ Observer/InstallmentDataAssignObserver.php | 6 +- .../APMBuilders/InstallmentAPMBuilderTest.php | 43 ++++ .../method-renderer/omise-cc-method.js | 152 ++++++++------ .../omise-offsite-installment-method.js | 190 +++++++++++++++++- .../payment/offsite-installment-form.html | 30 ++- 7 files changed, 372 insertions(+), 82 deletions(-) create mode 100644 Test/Unit/Gateway/Request/APMBuilders/InstallmentAPMBuilderTest.php diff --git a/Gateway/Request/APMBuilder.php b/Gateway/Request/APMBuilder.php index e855febf8..9e5d98ec8 100644 --- a/Gateway/Request/APMBuilder.php +++ b/Gateway/Request/APMBuilder.php @@ -45,6 +45,11 @@ class APMBuilder implements BuilderInterface { + /** + * @var string + */ + const CARD = 'card'; + /** * @var string */ @@ -187,13 +192,15 @@ public function build(array $buildSubject) ]; break; case Installment::CODE: - $installmentId = $method->getAdditionalInformation(InstallmentDataAssignObserver::OFFSITE); - $paymentInfo[self::SOURCE] = [ - self::SOURCE_TYPE => $installmentId, - self::SOURCE_INSTALLMENT_TERMS => $method->getAdditionalInformation( - InstallmentDataAssignObserver::TERMS - ) - ]; + $card = $method->getAdditionalInformation(InstallmentDataAssignObserver::CARD); + if ($card !== null) { + $paymentInfo[self::CARD] = $card; + } + + $source = $method->getAdditionalInformation(InstallmentDataAssignObserver::SOURCE); + if ($source !== null) { + $paymentInfo[self::SOURCE] = $source; + } break; case Truemoney::CODE: $paymentInfo[self::SOURCE] = $this->getTruemoneySourceData($method); diff --git a/Gateway/Request/PaymentDataBuilder.php b/Gateway/Request/PaymentDataBuilder.php index 94496e251..8512aa188 100644 --- a/Gateway/Request/PaymentDataBuilder.php +++ b/Gateway/Request/PaymentDataBuilder.php @@ -115,6 +115,18 @@ public function build(array $buildSubject) $requestBody[self::METADATA]['secure_form_enabled'] = $this->ccConfig->getSecureForm(); } + if (Installment::CODE === $method->getMethod()) { + $card = $method->getAdditionalInformation(InstallmentDataAssignObserver::CARD); + if ($card !== null) { + $requestBody['card'] = $card; + } + + $source = $method->getAdditionalInformation(InstallmentDataAssignObserver::SOURCE); + if ($source !== null) { + $requestBody['source'] = $source; + } + } + return $requestBody; } diff --git a/Observer/InstallmentDataAssignObserver.php b/Observer/InstallmentDataAssignObserver.php index f49efe790..09c20fa06 100644 --- a/Observer/InstallmentDataAssignObserver.php +++ b/Observer/InstallmentDataAssignObserver.php @@ -16,12 +16,16 @@ class InstallmentDataAssignObserver extends OffsiteDataAssignObserver * @var string */ const TERMS = 'terms'; + const CARD = 'card'; + const SOURCE = 'source'; /** * @var array */ protected $additionalInformationList = [ self::OFFSITE, - self::TERMS + self::TERMS, + self::CARD, + self::SOURCE, ]; } diff --git a/Test/Unit/Gateway/Request/APMBuilders/InstallmentAPMBuilderTest.php b/Test/Unit/Gateway/Request/APMBuilders/InstallmentAPMBuilderTest.php new file mode 100644 index 000000000..20c520935 --- /dev/null +++ b/Test/Unit/Gateway/Request/APMBuilders/InstallmentAPMBuilderTest.php @@ -0,0 +1,43 @@ +infoMock->method('getMethod')->willReturn(Installment::CODE); + $this->returnUrlHelper->method('create')->willReturn([ + 'url' => 'https://omise.co/complete', + 'card' => 'mock_card', + 'source' => 'mock_source', + 'token' => 'mock_token' + ]); + $this->infoMock->method('getAdditionalInformation')->willReturn('mock_source'); + + $this->builder = new APMBuilder( + $this->returnUrlHelper, + $this->config, + $this->capabilities, + new OmiseMoney(), + $this->requestHelper + ); + + $result = $this->builder->build(['payment' => new PaymentDataObject( + $this->orderMock, + $this->infoMock + )]); + + $this->assertEquals('mock_source', $result['source']); + $this->assertEquals('https://omise.co/complete', $result['return_uri']); + } +} diff --git a/view/frontend/web/js/view/payment/method-renderer/omise-cc-method.js b/view/frontend/web/js/view/payment/method-renderer/omise-cc-method.js index 6a53345fc..3be8be395 100644 --- a/view/frontend/web/js/view/payment/method-renderer/omise-cc-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/omise-cc-method.js @@ -8,7 +8,9 @@ define( 'Magento_Payment/js/model/credit-card-validation/validator', 'Magento_Checkout/js/model/full-screen-loader', 'Magento_Checkout/js/action/redirect-on-success', - 'Magento_Checkout/js/model/quote' + 'Magento_Checkout/js/model/quote', + 'Magento_Checkout/js/checkout-data', + 'Magento_Checkout/js/action/select-payment-method' ], function ( ko, @@ -19,7 +21,9 @@ define( validator, fullScreenLoader, redirectOnSuccessAction, - quote + quote, + checkoutData, + selectPaymentMethodAction ) { 'use strict' @@ -85,80 +89,98 @@ define( return this }, + selectPaymentMethod: function () { + this._super(); + selectPaymentMethodAction(this.getData()); + checkoutData.setSelectedPaymentMethod(this.item.method); + OmiseCard.destroy(); + setTimeout(() => { + if (this.isSecureForm()) { + const element = document.querySelector('.omise-card-form') + if(element) { + this.applyOmiseJsToElement(this, element) + } + } + }, 300); + + return true + }, + isSecureForm: function () { return window.checkoutConfig.payment.omise_cc.secureForm === 'yes' }, openOmiseJs: function () { - const self = this ko.bindingHandlers.omiseCardForm = { - init: function (element) { - const hideRememberCard = !self.isCustomerLoggedIn() - const iframeHeightMatching = { - '40px': 258, - '44px': 270, - '48px': 282, - '52px': 295, - } + init: (element) => this.applyOmiseJsToElement(this, element) + } + }, + + applyOmiseJsToElement: function (self, element) { + const hideRememberCard = !self.isCustomerLoggedIn() + const iframeHeightMatching = { + '40px': 258, + '44px': 270, + '48px': 282, + '52px': 295, + } - const localeMatching = { - en_US: 'en', - ja_JP: 'ja', - th_TH: 'th' + const localeMatching = { + en_US: 'en', + ja_JP: 'ja', + th_TH: 'th' + } + + const { theme, locale, formDesign } = window.checkoutConfig.payment.omise_cc + const { font, input, checkbox } = formDesign + let iframeElementHeight = iframeHeightMatching[input.height] + if (hideRememberCard) { + iframeElementHeight = iframeElementHeight - 25 + } + element.style.height = iframeElementHeight + 'px' + + OmiseCard.configure({ + publicKey: self.getPublicKey(), + element, + locale: localeMatching[locale] ?? 'en', + customCardForm: true, + customCardFormTheme: theme, + style: { + fontFamily: font.name, + fontSize: font.size, + input: { + height: input.height, + borderRadius: input.border_radius, + border: `1.2px solid ${input.border_color}`, + focusBorder: `1.2px solid ${input.active_border_color}`, + background: input.background_color, + color: input.text_color, + labelColor: input.label_color, + placeholderColor: input.placeholder_color, + }, + checkBox: { + textColor: checkbox.text_color, + themeColor: checkbox.theme_color, + border: `1.2px solid ${input.border_color}`, } + }, + customCardFormHideRememberCard: hideRememberCard + }) - const { theme, locale, formDesign } = window.checkoutConfig.payment.omise_cc - const { font, input, checkbox } = formDesign - let iframeElementHeight = iframeHeightMatching[input.height] - if (hideRememberCard) { - iframeElementHeight = iframeElementHeight - 25 + OmiseCard.open({ + onCreateTokenSuccess: (payload) => { + self.createOrder(self, payload) + }, + onError: (err) => { + if (err.length > 0) { + self.omiseCardError(err.length == 1 ? err[0] : 'Please enter required card information.') } - element.style.height = iframeElementHeight + 'px' - - OmiseCard.configure({ - publicKey: self.getPublicKey(), - element, - locale: localeMatching[locale] ?? 'en', - customCardForm: true, - customCardFormTheme: theme, - style: { - fontFamily: font.name, - fontSize: font.size, - input: { - height: input.height, - borderRadius: input.border_radius, - border: `1.2px solid ${input.border_color}`, - focusBorder: `1.2px solid ${input.active_border_color}`, - background: input.background_color, - color: input.text_color, - labelColor: input.label_color, - placeholderColor: input.placeholder_color, - }, - checkBox: { - textColor: checkbox.text_color, - themeColor: checkbox.theme_color, - border: `1.2px solid ${input.border_color}`, - } - }, - customCardFormHideRememberCard: hideRememberCard - }) - - OmiseCard.open({ - onCreateTokenSuccess: (payload) => { - self.createOrder(self, payload) - }, - onError: (err) => { - if (err.length > 0) { - self.omiseCardError(err.length == 1 ? err[0] : 'Please enter required card information.') - } - else { - self.omiseCardError('Something went wrong. Please refresh the page and try again.') - } - self.stopPerformingPlaceOrderAction() - } - }) + else { + self.omiseCardError('Something went wrong. Please refresh the page and try again.') + } + self.stopPerformingPlaceOrderAction() } - } + }) }, createOrder: function (self, payload) { diff --git a/view/frontend/web/js/view/payment/method-renderer/omise-offsite-installment-method.js b/view/frontend/web/js/view/payment/method-renderer/omise-offsite-installment-method.js index aa5da1611..2a0a6b779 100644 --- a/view/frontend/web/js/view/payment/method-renderer/omise-offsite-installment-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/omise-offsite-installment-method.js @@ -6,6 +6,10 @@ define( 'Magento_Checkout/js/view/payment/default', 'Magento_Checkout/js/model/quote', 'Magento_Catalog/js/price-utils', + 'Magento_Checkout/js/model/full-screen-loader', + 'mage/storage', + 'Magento_Checkout/js/checkout-data', + 'Magento_Checkout/js/action/select-payment-method' ], function ( $, @@ -13,7 +17,11 @@ define( Base, Component, quote, - priceUtils + priceUtils, + fullScreenLoader, + storage, + checkoutData, + selectPaymentMethodAction ) { 'use strict'; const CAPTION = $.mage.__('Choose number of monthly payments'); @@ -83,15 +91,28 @@ define( }, ] + function convertToCents(dollarAmount) { + return Math.round(parseFloat(dollarAmount) * 100); + } + return Component.extend(Base).extend({ defaults: { template: 'Omise_Payment/payment/offsite-installment-form' }, code: 'omise_offsite_installment', restrictedToCurrencies: ['thb', 'myr'], - + isPlaceOrderActionAllowed: ko.observable(quote.billingAddress() != null), capabilities: null, + billingAddressCountries: ["US", "GB", "CA"], + /** + * Get Omise public key + * + * @return {string} + */ + getPublicKey: function () { + return window.checkoutConfig.payment.omise_cc.publicKey + }, /** * Initiate observable fields * @@ -110,16 +131,139 @@ define( 'installmentTermsUOB', 'installmentTermsMBB', 'installmentTermsTTB', + 'omiseInstallmentError', + 'omiseInstallmentToken', + 'omiseInstallmentSource', ]); this.capabilities = checkoutConfig.omise_payment_list[this.code]; // filter provider for checkout page this.providers = this.get_available_providers() - + this.openOmiseJs(); return this; }, + selectPaymentMethod: function () { + this._super(); + selectPaymentMethodAction(this.getData()); + checkoutData.setSelectedPaymentMethod(this.item.method); + OmiseCard.destroy(); + setTimeout(() => { + const element = document.querySelector('.omise-installment-form') + if(element) { + this.applyOmiseJsToElement(this, element); + } + }, 300); + return true + }, + + openOmiseJs: function () { + ko.bindingHandlers.omiseInstallmentForm = { + init: (element) => this.applyOmiseJsToElement(this, element) + } + }, + + applyOmiseJsToElement: function (self, element) { + const iframeHeightMatching = { + '40px': 258, + '44px': 270, + '48px': 282, + '52px': 295, + } + + const localeMatching = { + en_US: 'en', + ja_JP: 'ja', + th_TH: 'th' + } + + const { theme, locale, formDesign } = window.checkoutConfig.payment.omise_cc + const { font, input, checkbox } = formDesign + let iframeElementHeight = iframeHeightMatching[input.height] + element.style.height = 500 + 'px'; + + OmiseCard.configure({ + publicKey: self.getPublicKey(), + amount: convertToCents(quote.totals().grand_total), + element, + iframeAppId: 'omise-checkout-installment-form', + customCardForm: false, + customInstallmentForm: true, + locale: localeMatching[locale] ?? 'en', + defaultPaymentMethod: 'installment' + }); + + OmiseCard.open({ + onCreateSuccess: (payload) => { + self.createOrder(self, payload) + }, + onError: (err) => { + if (err.length > 0) { + self.omiseInstallmentError(err.length == 1 ? err[0] : 'Please enter required card information.') + } + else { + self.omiseInstallmentError('Something went wrong. Please refresh the page and try again.') + } + self.stopPerformingPlaceOrderAction() + } + }); + }, + + createOrder: function (self, payload) { + self.omiseInstallmentToken(payload.token) + self.omiseInstallmentSource(payload.source) + const failHandler = self.buildFailHandler(this, 300) + self.getPlaceOrderDeferredObject() + .fail(failHandler) + .done((order_id) => { + let serviceUrl = self.getMagentoReturnUrl(order_id) + storage.get(serviceUrl, false) + .fail(failHandler) + .done(function (response) { + if (response) { + $.mage.redirect(response.authorize_uri) + } else { + failHandler(response) + } + }) + }) + }, + + /** + * Hook the placeOrder function. + * Original source: placeOrder(data, event); @ module-checkout/view/frontend/web/js/view/payment/default.js + * + * @return {boolean} + */ + placeOrder: function (data, event) { + this.omiseInstallmentError(null) + event && event.preventDefault() + + if (typeof Omise === 'undefined') { + alert($.mage.__('Unable to process the payment, loading the external card processing library is failed. Please contact the merchant.')) + return false + } + + this.generateTokenWithEmbeddedFormAndPerformPlaceOrderAction() + return true + }, + + /** + * Generate Omise token with embedded form before proceed the placeOrder process. + * + * @return {void} + */ + generateTokenWithEmbeddedFormAndPerformPlaceOrderAction: function () { + this.startPerformingPlaceOrderAction() + let billingAddress = {} + let selectedBillingAddress = quote.billingAddress() + if (this.billingAddressCountries.indexOf(selectedBillingAddress.countryId) > -1) { + Object.assign(billingAddress, this.getSelectedTokenBillingAddress(selectedBillingAddress)) + } + OmiseCard.requestCardToken(billingAddress) + }, + /** * Get installment min amount from capability * @@ -315,8 +459,8 @@ define( return { 'method': this.item.method, 'additional_data': { - 'offsite': this.omiseOffsite(), - 'terms': this.getTerms() + 'card': this.omiseInstallmentToken(), + 'source': this.omiseInstallmentSource() } }; }, @@ -380,8 +524,42 @@ define( } } ))) - } + }, + + /** + * Start performing place order action, + * by disable a place order button and show full screen loader component. + */ + startPerformingPlaceOrderAction: function () { + this.isPlaceOrderActionAllowed(false) + fullScreenLoader.startLoader() + }, + + /** + * Stop performing place order action, + * by disable a place order button and show full screen loader component. + */ + stopPerformingPlaceOrderAction: function () { + fullScreenLoader.stopLoader() + this.isPlaceOrderActionAllowed(true) + }, + + getSelectedTokenBillingAddress: function (selectedBillingAddress) { + let address = { + state: selectedBillingAddress.region, + postal_code: selectedBillingAddress.postcode, + phone_number: selectedBillingAddress.telephone, + country: selectedBillingAddress.countryId, + city: selectedBillingAddress.city, + street1: selectedBillingAddress.street[0] + } + if (selectedBillingAddress.street[1]) { + address.street2 = selectedBillingAddress.street[1] + } + + return address + } }); } ); diff --git a/view/frontend/web/template/payment/offsite-installment-form.html b/view/frontend/web/template/payment/offsite-installment-form.html index 50c004e35..e16d0d7ce 100644 --- a/view/frontend/web/template/payment/offsite-installment-form.html +++ b/view/frontend/web/template/payment/offsite-installment-form.html @@ -39,8 +39,17 @@ + + +
+ +
+ + -
+ + + +
@@ -96,7 +120,7 @@ click: placeOrder, attr: {title: $t('Place Order')}, css: {disabled: !isPlaceOrderActionAllowed()}, - enable: (getCode() == isChecked()) && omiseOffsite() && getTerms() "> + enable: (getCode() == isChecked())">