PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | paypal/js/frontend.js +132 -20 6.32.1 → trunk View file →
@@ -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', {
@@ -919,9 +921,9 @@
919 921 * @return {google.payments.api.PaymentsClient} The payments client instance.
920 922 */
921 923 function getGooglePaymentsClient() {
922 924 return new google.payments.api.PaymentsClient( {
923 - environment: 'TEST',
925 + environment: frmPayPalVars.mode === 'test' ? 'TEST' : 'PRODUCTION',
924 926 paymentDataCallbacks: {
925 927 onPaymentAuthorized
926 928 }
927 929 } );
@@ -1567,9 +1569,13 @@
1567 1569 return fallback;
1568 1570 }
1569 1571
1570 1572 if ( 'string' === typeof err ) {
1571 - return parsePayPalErrorString( err ) || err;
1573 + const parsed = parsePayPalErrorString( err );
1574 + if ( parsed ) {
1575 + return parsed;
1576 + }
1577 + return mapPayPalErrorCode( err ) || err;
1572 1578 }
1573 1579
1574 1580 // PayPal SDK sometimes nests the payload under `err.data` or `err.response`.
1575 1581 const payloads = [ err, err.data, err.response ].filter( Boolean );
@@ -1580,9 +1586,13 @@
1580 1586 }
1581 1587 }
1582 1588
1583 1589 if ( err.message ) {
1584 - return parsePayPalErrorString( err.message ) || err.message;
1590 + const parsed = parsePayPalErrorString( err.message );
1591 + if ( parsed ) {
1592 + return parsed;
1593 + }
1594 + return mapPayPalErrorCode( err.message ) || err.message;
1585 1595 }
1586 1596
1587 1597 return fallback;
1588 1598 }
@@ -1587,8 +1597,25 @@
1587 1597 return fallback;
1588 1598 }
1589 1599
1590 1600 /**
1601 + * Map PayPal error codes to user-friendly messages.
1602 + *
1603 + * @param {string} code The PayPal error code (e.g. INVALID_CVV).
1604 + * @return {string} The user-friendly message, or empty string if not mapped.
1605 + */
1606 + function mapPayPalErrorCode( code ) {
1607 + const codeMap = {
1608 + INVALID_CVV: 'Please enter a valid CVV code.',
1609 + INVALID_CARD_NUMBER: 'Please enter a valid card number.',
1610 + INVALID_EXPIRY: 'Please enter a valid expiry date.',
1611 + };
1612 +
1613 + const upperCode = code.toUpperCase();
1614 + return codeMap[ upperCode ] || '';
1615 + }
1616 +
1617 + /**
1591 1618 * Extract the first `description` from a PayPal `details` array.
1592 1619 *
1593 1620 * @param {Array} details The PayPal error details array.
1594 1621 * @return {string} The description, or an empty string if none.
@@ -1752,21 +1779,13 @@
1752 1779 if ( meta.name ) {
1753 1780 submitArgs.cardholderName = meta.name;
1754 1781 }
1755 1782
1756 - /*
1757 - TODO Add the billing address here as well.
1758 - Stripe calls a window.frmProForm.addAddressMeta function.
1759 - That's included in frmstrp.js though, so we need to add a script in Pro for PayPal as well.
1783 + const billingAddress = getBillingAddress();
1760 1784
1761 - billingAddress: {
1762 - addressLine1: '555 Billing Ave',
1763 - adminArea1: 'NY',
1764 - adminArea2: 'New York',
1765 - postalCode: '10001',
1766 - countryCode: 'US'
1785 + if ( billingAddress ) {
1786 + submitArgs.billingAddress = billingAddress;
1767 1787 }
1768 - */
1769 1788
1770 1789 try {
1771 1790 await cardFieldsInstance.submit( submitArgs );
1772 1791 } catch ( err ) {
@@ -1776,8 +1795,101 @@
1776 1795 enableSubmit();
1777 1796 }
1778 1797 reportErrorToServer( err, 'card_submit' );
1779 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' : '';
1780 1892 }
1781 1893
1782 1894 // ---- Price / Pay Later ----
1783 1895