diff --git a/src/Model/AutocompleteConfigProvider.php b/src/Model/AutocompleteConfigProvider.php
index 36c26ed..4d43841 100644
--- a/src/Model/AutocompleteConfigProvider.php
+++ b/src/Model/AutocompleteConfigProvider.php
@@ -62,7 +62,8 @@ public function getConfig()
'active' => $this->helper->getConfigValue('shipping/shipper_autocomplete/active'),
'api_key' => $this->helper->getConfigValue('shipping/shipper_autocomplete/google_api_key'),
'use_geolocation' => $this->helper->getConfigValue('shipping/shipper_autocomplete/use_geolocation'),
- 'use_long_postcode' => $this->helper->getConfigValue('shipping/shipper_autocomplete/use_long_postcode')
+ 'use_long_postcode' => $this->helper->getConfigValue('shipping/shipper_autocomplete/use_long_postcode'),
+ 'billing_autocomplete' => $this->helper->getConfigValue('shipping/shipper_autocomplete/autocomplete_billing_address')
];
return $config;
}
diff --git a/src/Plugin/AddCheckoutBillingAddressAutocomplete.php b/src/Plugin/AddCheckoutBillingAddressAutocomplete.php
new file mode 100644
index 0000000..04a3cd1
--- /dev/null
+++ b/src/Plugin/AddCheckoutBillingAddressAutocomplete.php
@@ -0,0 +1,82 @@
+checkoutHelper = $checkoutHelper;
+ }
+
+ /**
+ * Adds the billing address autocomplete component to the billing address forms in checkout
+ *
+ * @see LayoutProcessor::process()
+ * @param LayoutProcessor $subject
+ * @param array $jsLayout
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterProcess(LayoutProcessor $subject, $jsLayout)
+ {
+ if (!isset(
+ $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
+ ['payment']['children']
+ )) {
+ return $jsLayout;
+ }
+
+ if ($this->checkoutHelper->isDisplayBillingOnPaymentMethodAvailable()) {
+ $configuration = &$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']
+ ['children']['payment']['children']['payments-list']['children'];
+
+ foreach ($configuration as $paymentFormCode => $formConfiguration) {
+ if (strpos($paymentFormCode, '-form') !== false) {
+ $paymentCode = str_replace('-form', '', $paymentFormCode);
+ $configuration[$paymentFormCode]['children']
+ [$paymentCode . '-billing-address-autocomplete'] = $this->getBillingAddressAutocompleteComponent(
+ $paymentCode
+ );
+ }
+ }
+ } else {
+ $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']
+ ['children']['payment']['children']['afterMethods']['children']['billing-address-form']['children']
+ ['shared-billing-address-autocomplete'] = $this->getBillingAddressAutocompleteComponent(
+ 'shared'
+ );
+ }
+
+ return $jsLayout;
+ }
+
+ /**
+ * Returns that component configuration to be used in the checkout
+ *
+ * @param string $paymentCode
+ * @return array
+ */
+ private function getBillingAddressAutocompleteComponent($paymentCode)
+ {
+ return [
+ 'component' => 'ShipperHQ_AddressAutocomplete/js/billing-address-autocomplete',
+ 'config' => [
+ 'paymentCode' => $paymentCode
+ ]
+ ];
+ }
+}
\ No newline at end of file
diff --git a/src/etc/adminhtml/system.xml b/src/etc/adminhtml/system.xml
index 2fba1ca..d6d74a9 100644
--- a/src/etc/adminhtml/system.xml
+++ b/src/etc/adminhtml/system.xml
@@ -79,6 +79,13 @@
]]>
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+
+
+
diff --git a/src/etc/config.xml b/src/etc/config.xml
index c2bf26f..6762ddd 100755
--- a/src/etc/config.xml
+++ b/src/etc/config.xml
@@ -39,6 +39,7 @@
1
0
+ 0
diff --git a/src/etc/frontend/di.xml b/src/etc/frontend/di.xml
index ecaaa68..b13d5b5 100644
--- a/src/etc/frontend/di.xml
+++ b/src/etc/frontend/di.xml
@@ -17,4 +17,8 @@
+
+
+
+
diff --git a/src/view/frontend/web/js/billing-address-autocomplete.js b/src/view/frontend/web/js/billing-address-autocomplete.js
new file mode 100644
index 0000000..b95bf9e
--- /dev/null
+++ b/src/view/frontend/web/js/billing-address-autocomplete.js
@@ -0,0 +1,290 @@
+define([
+ 'jquery',
+ 'ko',
+ 'uiComponent',
+ 'ShipperHQ_AddressAutocomplete/js/google_maps_loader',
+ 'Magento_Checkout/js/checkout-data',
+ 'Magento_Customer/js/model/customer',
+ 'uiRegistry'
+], function (
+ $,
+ ko,
+ Component,
+ GoogleMapsLoader,
+ checkoutData,
+ customer,
+ uiRegistry
+) {
+ 'use strict';
+
+ return Component.extend({
+ defaults: {
+ imports: {
+ isAddressFormVisible: '${$.parentName}:isAddressFormVisible',
+ isAddressSameAsShipping: '${$.parentName}:isAddressSameAsShipping'
+ }
+ },
+
+ componentForm: {
+ subpremise: 'short_name',
+ street_number: 'short_name',
+ route: 'long_name',
+ locality: 'long_name',
+ administrative_area_level_1: 'long_name',
+ country: 'short_name',
+ postal_code: 'short_name',
+ postal_code_suffix: 'short_name',
+ postal_town: 'short_name',
+ sublocality_level_1: 'short_name'
+ },
+
+ lookupElement: {
+ street_number: 'street_1',
+ route: 'street_2',
+ locality: 'city',
+ administrative_area_level_1: 'region',
+ country: 'country_id',
+ postal_code: 'postcode'
+ },
+
+ isAddressFormVisible: ko.observable(false),
+ isAddressSameAsShipping: ko.observable(false),
+ isGoogleMapsLoaderLoaded: ko.observable(false),
+ autocompleteReadyToInit: ko.observable(false),
+ isAutocompleteInitialized: ko.observable(false),
+
+ initialize: function () {
+ this._super();
+ this.moduleEnabled = window.checkoutConfig.shipperhq_autocomplete.active;
+ this.autocompleteBillingAddress = window.checkoutConfig.shipperhq_autocomplete.billing_autocomplete;
+ this.googleMapError = false;
+
+ this.formComponent = this.paymentCode === 'shared'
+ ? 'checkout.steps.billing-step.payment.afterMethods.billing-address-form.form-fields'
+ : 'checkout.steps.billing-step.payment.payments-list.' + this.paymentCode + '-form.form-fields';
+
+ var self = this;
+
+ window.gm_authFailure = function () {
+ self.googleMapError = true;
+ };
+
+ GoogleMapsLoader.done(function () {
+ self.isGoogleMapsLoaderLoaded(true);
+ }).fail(function () {
+ console.error("ERROR: Google maps library failed to load");
+ });
+
+ this.isGoogleMapsLoaderLoaded.subscribe(function (newValue) {
+ if (customer.isLoggedIn()) {
+ self.autocompleteReadyToInit(newValue && self.isAddressFormVisible());
+ } else {
+ self.autocompleteReadyToInit(newValue && !self.isAddressSameAsShipping());
+ }
+ });
+
+ if (customer.isLoggedIn()) {
+ this.isAddressFormVisible.subscribe(function (newValue) {
+ self.autocompleteReadyToInit(newValue && self.isGoogleMapsLoaderLoaded());
+ });
+ } else {
+ this.isAddressSameAsShipping.subscribe(function (newValue) {
+ self.autocompleteReadyToInit(!newValue && self.isGoogleMapsLoaderLoaded());
+ });
+ }
+
+ this.autocompleteReadyToInit.subscribe(function (newValue) {
+ if (!newValue || self.isAutocompleteInitialized()) {
+ return;
+ }
+
+ self.initAutocomplete();
+ });
+
+ },
+
+ initAutocomplete: function () {
+ var self = this;
+ var geocoder = new google.maps.Geocoder();
+
+ if ((self.moduleEnabled !== '1' && self.googleMapError) || self.autocompleteBillingAddress !== '1') {
+ return;
+ }
+
+ setTimeout(function () {
+ var domID = uiRegistry.get(self.formComponent + '.street').elems()[0].uid;
+ var street = $('#' + domID);
+
+ //SHQ18-260
+ var observer = new MutationObserver(function () {
+ observer.disconnect();
+ $("#" + domID).attr("autocomplete", "new-password");
+ });
+
+ street.each(function () {
+ var element = this;
+
+ observer.observe(element, {
+ attributes: true,
+ attributeFilter: ['autocomplete']
+ });
+
+ self.autocomplete = new google.maps.places.Autocomplete(
+ /** @type {!HTMLInputElement} */(this),
+ {types: ['geocode']}
+ );
+ self.autocomplete.addListener('place_changed', self.fillInAddress.bind(self, self.formComponent));
+
+ });
+ $('#' + domID).focus(self.geolocate.bind(this));
+
+ self.isAutocompleteInitialized(true);
+ }, 5000);
+ },
+
+ fillInAddress: function (formFieldPrefix) {
+ var place = this.autocomplete.getPlace();
+
+ var street = [];
+ var region = '';
+ var streetNumber = '';
+ var city = '';
+ var postcode = '';
+ var postcodeSuffix = '';
+ var subpremise = ''; // This is apartment/unit/flat number etc
+ var countryId = '';
+
+ // MNB-574 Some European countries place the house number after the street name rather than before.
+ var numberAfterStreetCountries = ['AT', 'BE', 'DK', 'DE', 'GR', 'IS', 'IT', 'NL', 'NO', 'PT', 'ES', 'SE', 'CH'];
+ var numberAfterStreet = false;
+
+ for (var i = 0; i < place.address_components.length; i++) {
+ var addressType = place.address_components[i].types[0];
+ if (this.componentForm[addressType]) {
+ var value = place.address_components[i][this.componentForm[addressType]];
+ if (addressType == 'subpremise') {
+ subpremise = value;
+ } else if (addressType == 'street_number') {
+ streetNumber = value;
+ } else if (addressType == 'route') {
+ street[1] = value;
+ } else if (addressType == 'administrative_area_level_1') {
+ region = value;
+ } else if (addressType == 'sublocality_level_1') {
+ city = value;
+ } else if (addressType == 'postal_town') {
+ city = value;
+ } else if (addressType == 'locality' && (city === '' || value === 'Montréal')) {
+ //ignore if we are using one of other city values already
+ // MNB-2364 Google returns sublocality_level_1 for Montreal. Always want to use Montreal
+ city = value;
+ } else if (addressType == 'postal_code') {
+ postcode = value;
+ var thisDomID = uiRegistry.get(formFieldPrefix + '.postcode').uid
+ if ($('#'+thisDomID).length) {
+ $('#'+thisDomID).val(postcode + postcodeSuffix);
+ $('#'+thisDomID).trigger('change');
+ }
+ } else if (addressType == 'postal_code_suffix' && window.checkoutConfig.shipperhq_autocomplete.use_long_postcode === '1') {
+ postcodeSuffix = '-' + value;
+ var thisDomID = uiRegistry.get(formFieldPrefix + '.postcode').uid
+ if ($('#'+thisDomID).length) {
+ $('#'+thisDomID).val(postcode + postcodeSuffix);
+ $('#'+thisDomID).trigger('change');
+ }
+ } else {
+ var elementId = this.lookupElement[addressType];
+ if (elementId !== undefined) {
+ var thisDomID = uiRegistry.get(formFieldPrefix + '.' + elementId).uid;
+ if ($('#' + thisDomID).length) {
+ $('#' + thisDomID).val(value);
+ $('#' + thisDomID).trigger('change');
+ }
+
+ if (elementId === 'country_id') {
+ countryId = value;
+ numberAfterStreet = numberAfterStreetCountries.includes(countryId);
+ }
+ }
+ }
+ }
+ }
+
+ // SHQ23-326 US Address Format is street address, unit or apartment number
+ if (subpremise.length > 0 && countryId !== 'US') {
+ streetNumber = subpremise + '/' + streetNumber;
+ }
+
+ if (street.length > 0) {
+ if (numberAfterStreet) {
+ street[0] = street[1];
+ street[1] = streetNumber;
+ } else {
+ street[0] = streetNumber;
+ }
+
+ var domID = uiRegistry.get(formFieldPrefix + '.street').elems()[0].uid;
+ var streetString = street.join(' ');
+
+ if (countryId === 'US' && subpremise !== '') {
+ streetString += ', ' + subpremise
+ }
+
+ if ($('#' + domID).length) {
+ $('#' + domID).val(streetString);
+ $('#' + domID).trigger('change');
+ }
+ }
+
+ var cityDomID = uiRegistry.get(formFieldPrefix + '.city').uid;
+ if ($('#'+cityDomID).length) {
+ $('#'+cityDomID).val(city);
+ $('#'+cityDomID).trigger('change');
+ }
+ if (region != '') {
+ // MNB-1966 AutoComplete does not fill in Quebec field when an accent mark is returned from Google.
+ if (region === 'Québec') {
+ region = 'Quebec'
+ }
+
+ if (uiRegistry.get(formFieldPrefix + '.region_id')) {
+ var regionDomId = uiRegistry.get(formFieldPrefix + '.region_id').uid;
+ if ($('#'+regionDomId).length) {
+ //search for and select region using text
+ $('#'+regionDomId +' option')
+ .filter(function () {
+ return $.trim($(this).text()) == region;
+ })
+ .attr('selected',true);
+ $('#'+regionDomId).trigger('change');
+ }
+ }
+ if (uiRegistry.get(formFieldPrefix + '.region_id_input')) {
+ var regionDomId = uiRegistry.get(formFieldPrefix + '.region_id_input').uid;
+ if ($('#'+regionDomId).length) {
+ $('#'+regionDomId).val(region);
+ $('#'+regionDomId).trigger('change');
+ }
+ }
+ }
+ },
+
+ geolocate: function () {
+ var self = this;
+
+ if (navigator.geolocation && window.checkoutConfig.shipperhq_autocomplete.use_geolocation === '1') {
+ navigator.geolocation.getCurrentPosition(function (position) {
+ var geolocation = {
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ };
+ var circle = new google.maps.Circle({
+ center: geolocation,
+ radius: position.coords.accuracy
+ });
+ self.autocomplete.setBounds(circle.getBounds());
+ });
+ }
+ }
+ });
+});
\ No newline at end of file