| @@ -302,11 +302,11 @@ | ||
| 302 | 302 | if ( ! buttonsAreEnabled || isRecurring ) { |
| 303 | 303 | return; |
| 304 | 304 | } |
| 305 | 305 | |
| 306 | - // Skip (and avoid the SDK wait) when Google Pay / Apple Pay were not enqueued | |
| 307 | - // server-side, e.g. via the frm_include_google_pay_apple_pay filter or non-SSL. | |
| 308 | - if ( ! frmPayPalVars.includeGooglePayApplePay ) { | |
| 306 | + // Skip (and avoid the SDK wait) when neither wallet was enqueued server-side, | |
| 307 | + // e.g. via the frm_include_google_pay_apple_pay filter or non-SSL. | |
| 308 | + if ( ! frmPayPalVars.includeGooglePay && ! frmPayPalVars.includeApplePay ) { | |
| 309 | 309 | return; |
| 310 | 310 | } |
| 311 | 311 | |
| 312 | 312 | // Resolve both eligibility checks in parallel so the combined wait is bounded by |
| @@ -311,12 +311,14 @@ | ||
| 311 | 311 | |
| 312 | 312 | // Resolve both eligibility checks in parallel so the combined wait is bounded by |
| 313 | 313 | // the slower of the two, then register them in a fixed order (Google Pay, then |
| 314 | 314 | // Apple Pay). Registration happens before the selector is built, so they render |
| 315 | - // together with the other methods. | |
| 315 | + // together with the other methods. A wallet turned off server-side, e.g. via | |
| 316 | + // frm_paypal_commerce_include_google_pay or frm_paypal_commerce_include_apple_pay, | |
| 317 | + // skips its check so it never waits on an SDK that was never enqueued. | |
| 316 | 318 | const [ googlePayEligible, applePayEligible ] = await Promise.all( [ |
| 317 | - resolveGooglePayEligibility(), | |
| 318 | - resolveApplePayEligibility() | |
| 319 | + frmPayPalVars.includeGooglePay ? resolveGooglePayEligibility() : false, | |
| 320 | + frmPayPalVars.includeApplePay ? resolveApplePayEligibility() : false | |
| 319 | 321 | ] ); |
| 320 | 322 | |
| 321 | 323 | if ( googlePayEligible ) { |
| 322 | 324 | registerMethod( 'google_pay', { |
| @@ -1777,21 +1779,13 @@ | ||
| 1777 | 1779 | if ( meta.name ) { |
| 1778 | 1780 | submitArgs.cardholderName = meta.name; |
| 1779 | 1781 | } |
| 1780 | 1782 | |
| 1781 | - /* | |
| 1782 | - TODO Add the billing address here as well. | |
| 1783 | - Stripe calls a window.frmProForm.addAddressMeta function. | |
| 1784 | - That's included in frmstrp.js though, so we need to add a script in Pro for PayPal as well. | |
| 1783 | + const billingAddress = getBillingAddress(); | |
| 1785 | 1784 | |
| 1786 | - billingAddress: { | |
| 1787 | - addressLine1: '555 Billing Ave', | |
| 1788 | - adminArea1: 'NY', | |
| 1789 | - adminArea2: 'New York', | |
| 1790 | - postalCode: '10001', | |
| 1791 | - countryCode: 'US' | |
| 1785 | + if ( billingAddress ) { | |
| 1786 | + submitArgs.billingAddress = billingAddress; | |
| 1792 | 1787 | } |
| 1793 | - */ | |
| 1794 | 1788 | |
| 1795 | 1789 | try { |
| 1796 | 1790 | await cardFieldsInstance.submit( submitArgs ); |
| 1797 | 1791 | } catch ( err ) { |
| @@ -1801,8 +1795,101 @@ | ||
| 1801 | 1795 | enableSubmit(); |
| 1802 | 1796 | } |
| 1803 | 1797 | reportErrorToServer( err, 'card_submit' ); |
| 1804 | 1798 | } |
| 1799 | + } | |
| 1800 | + | |
| 1801 | + /** | |
| 1802 | + * Build the billing address for the card fields from the address field mapped in the payment action. | |
| 1803 | + * | |
| 1804 | + * @since 6.34 | |
| 1805 | + * | |
| 1806 | + * @return {Object|null} Billing address for the card fields submit args, or null when no address is available. | |
| 1807 | + */ | |
| 1808 | + function getBillingAddress() { | |
| 1809 | + let addressID = ''; | |
| 1810 | + | |
| 1811 | + getPayPalSettings().forEach( function( setting ) { | |
| 1812 | + if ( setting.address ) { | |
| 1813 | + addressID = setting.address; | |
| 1814 | + } | |
| 1815 | + } ); | |
| 1816 | + | |
| 1817 | + if ( '' === addressID ) { | |
| 1818 | + return null; | |
| 1819 | + } | |
| 1820 | + | |
| 1821 | + let prefix = ''; | |
| 1822 | + let addressContainer = document.querySelector( `#frm_field_${ addressID }_container, .frm_field_${ addressID }_container` ); | |
| 1823 | + | |
| 1824 | + if ( ! addressContainer ) { | |
| 1825 | + const line1Input = document.querySelector( `input[name="item_meta[${ addressID }][line1]"]` ); | |
| 1826 | + if ( line1Input ) { | |
| 1827 | + prefix = `${ addressID }][`; | |
| 1828 | + addressContainer = line1Input.parentNode; | |
| 1829 | + } | |
| 1830 | + } | |
| 1831 | + | |
| 1832 | + if ( ! addressContainer ) { | |
| 1833 | + return null; | |
| 1834 | + } | |
| 1835 | + | |
| 1836 | + const getSubFieldValue = function( name ) { | |
| 1837 | + const input = addressContainer.querySelector( `input[name$="[${ prefix }${ name }]"], select[name$="[${ prefix }${ name }]"]` ); | |
| 1838 | + return input?.value ? input.value : ''; | |
| 1839 | + }; | |
| 1840 | + | |
| 1841 | + const subFieldMapping = { | |
| 1842 | + line1: 'addressLine1', | |
| 1843 | + line2: 'addressLine2', | |
| 1844 | + city: 'adminArea2', | |
| 1845 | + state: 'adminArea1', | |
| 1846 | + zip: 'postalCode' | |
| 1847 | + }; | |
| 1848 | + | |
| 1849 | + const billingAddress = {}; | |
| 1850 | + | |
| 1851 | + Object.keys( subFieldMapping ).forEach( function( name ) { | |
| 1852 | + const value = getSubFieldValue( name ); | |
| 1853 | + if ( value ) { | |
| 1854 | + billingAddress[ subFieldMapping[ name ] ] = value; | |
| 1855 | + } | |
| 1856 | + } ); | |
| 1857 | + | |
| 1858 | + if ( ! billingAddress.addressLine1 ) { | |
| 1859 | + return null; | |
| 1860 | + } | |
| 1861 | + | |
| 1862 | + const countryCode = getCountryCode( addressContainer, prefix ); | |
| 1863 | + | |
| 1864 | + if ( countryCode ) { | |
| 1865 | + billingAddress.countryCode = countryCode; | |
| 1866 | + } | |
| 1867 | + | |
| 1868 | + return billingAddress; | |
| 1869 | + } | |
| 1870 | + | |
| 1871 | + /** | |
| 1872 | + * Get the two letter country code for the filled address. | |
| 1873 | + * The country dropdown holds the code in a data-code attribute. | |
| 1874 | + * US type address fields have a state dropdown and no country field, so US is assumed for them. | |
| 1875 | + * | |
| 1876 | + * @since 6.34 | |
| 1877 | + * | |
| 1878 | + * @param {Element} addressContainer | |
| 1879 | + * @param {string} prefix | |
| 1880 | + * @return {string} Country code, or an empty string when the country is unknown. | |
| 1881 | + */ | |
| 1882 | + function getCountryCode( addressContainer, prefix ) { | |
| 1883 | + const countryDropdown = addressContainer.querySelector( `select[name$="[${ prefix }country]"]` ); | |
| 1884 | + | |
| 1885 | + if ( countryDropdown ) { | |
| 1886 | + const countryOption = countryDropdown.querySelector( `option[value="${ countryDropdown.value }"]` ); | |
| 1887 | + return countryOption?.getAttribute( 'data-code' ) || ''; | |
| 1888 | + } | |
| 1889 | + | |
| 1890 | + const stateDropdown = addressContainer.querySelector( `select[name$="[${ prefix }state]"]` ); | |
| 1891 | + return stateDropdown ? 'US' : ''; | |
| 1805 | 1892 | } |
| 1806 | 1893 | |
| 1807 | 1894 | // ---- Price / Pay Later ---- |
| 1808 | 1895 | |