diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 2fc5beb975e..79ff0d7a01f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: matrix: woocommerce: [ 'latest' ] wordpress: [ 'latest' ] - php: [ '7.4' ] + php: [ '7.4', '8.1' ] directory: [ 'includes', 'src' ] env: WP_VERSION: ${{ matrix.wordpress }} @@ -36,6 +36,6 @@ jobs: with: php-version: ${{ matrix.php }} tools: composer - coverage: xdebug2 + coverage: xdebug # run CI checks - run: bash bin/run-ci-tests-check-coverage.bash diff --git a/assets/images/icons/copy.svg b/assets/images/icons/copy.svg new file mode 100644 index 00000000000..ab7cb5136ba --- /dev/null +++ b/assets/images/icons/copy.svg @@ -0,0 +1 @@ + diff --git a/bin/check-test-coverage.sh b/bin/check-test-coverage.sh index b634be4bed7..ad30c93a0c6 100755 --- a/bin/check-test-coverage.sh +++ b/bin/check-test-coverage.sh @@ -17,11 +17,10 @@ docker compose exec -u www-data wordpress \ echo "Checking coverage..." -docker compose exec -u www-data wordpress \ - php -d xdebug.remote_autostart=on \ - /var/www/html/wp-content/plugins/woocommerce-payments/vendor/bin/phpunit \ - --configuration "/var/www/html/wp-content/plugins/woocommerce-payments/$CONFIGURATION_FILE" \ - --coverage-html /var/www/html/php-test-coverage \ - --coverage-clover /var/www/html/clover.xml +docker-compose exec -u www-data wordpress \ + /var/www/html/wp-content/plugins/woocommerce-payments/vendor/bin/phpunit \ + --configuration "/var/www/html/wp-content/plugins/woocommerce-payments/$CONFIGURATION_FILE" \ + --coverage-html /var/www/html/php-test-coverage \ + --coverage-clover /var/www/html/clover.xml ./vendor/bin/coverage-check docker/wordpress/clover.xml $COVERAGE diff --git a/bin/run-tests.sh b/bin/run-tests.sh index 97e23b957a4..cf194d39079 100755 --- a/bin/run-tests.sh +++ b/bin/run-tests.sh @@ -24,13 +24,11 @@ if $WATCH_FLAG; then # Change directory to WooCommerce Payments' root in order to have access to .phpunit-watcher.yml docker compose exec -u www-data wordpress bash -c \ "cd /var/www/html/wp-content/plugins/woocommerce-payments && \ - php -d xdebug.remote_autostart=on \ ./vendor/bin/phpunit-watcher watch --configuration ./phpunit.xml.dist $*" else echo "Running the tests..." - docker compose exec -u www-data wordpress \ - php -d xdebug.remote_autostart=on \ + docker-compose exec -u www-data wordpress \ /var/www/html/wp-content/plugins/woocommerce-payments/vendor/bin/phpunit \ --configuration /var/www/html/wp-content/plugins/woocommerce-payments/phpunit.xml.dist \ $* diff --git a/changelog.txt b/changelog.txt index b28578736fb..f7bccd8a1d5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,36 @@ *** WooPayments Changelog *** += 8.2.0 - 2024-09-11 = +* Add - add: test instructions icon animation +* Add - Added Embdedded KYC, currently behind feature flag. +* Fix - Avoid unnecessary account data cache refresh on WooPayments pages refresh. +* Fix - Check payment method is available before rendering it. +* Fix - Disables custom checkout field detection due to compatibility issues and false positives. +* Fix - Disables testing instructions clipboard button on HTTP sites when navigator.clipboard is undefined. +* Fix - fix: missing translations on testing instructions. +* Fix - fix: platform_global_theme_support_enabled undefined index +* Fix - fix: testing instructions dark theme support +* Fix - Fix caching with tracking cookie. +* Fix - Fixed an issue where the Connect page would scroll to the top upon clicking the Enable Sandbox Mode button. +* Fix - Fixed default borderRadius value for the express checkout buttons +* Fix - Fix shipping rates retrieval method for shortcode cart/checkout. +* Fix - Fix support for merchant site styling when initializing WooPay via classic checkout +* Fix - Fix WooPay direct checkout. +* Fix - Handle loadError in ECE for Block Context Initialization. +* Fix - Move woopay theme support checkbox to the appearance section. +* Fix - Pass appearance data when initiating WooPay via the email input flow +* Fix - Prevent preload of BNPL messaging if minimum order amount isn't hit. +* Fix - Redirect user to WooPay OTP when the email is saved. +* Fix - Remove obsolete ApplePay warning on wp-admin for test sites. +* Fix - Update cache after persisting the User session via WooPay +* Fix - Updates test mode instructions copy for cards at checkout. +* Update - update: payment method fees in one line +* Update - Update Jetpack packages to the latest versions +* Dev - Fix failing e2e tests for saved cards. +* Dev - Fix Klarna product page message E2E test after the contents inside the iframe were updated. +* Dev - Migrate Klarna E2E tests to playwright. Reduce noise in E2E tests console output. +* Dev - Migrate multi-currency e2e tests to Playwright. + = 8.1.1 - 2024-08-29 = * Fix - Ensure 55px is the maximum height for Apple Pay button. * Fix - Fixed sandbox mode accounts being able to disable test mode for the payment gateway settings. @@ -24,7 +55,6 @@ * Fix - Fix uncaught error on the block based Cart page when WooPayments is disabled. * Fix - Fix WooPay checkboxes while signed in. * Fix - If a payment method fails to be created in the frontend during checkout, forward the errors to the server so it can be recorded in an order. -* Fix - Migrate to Docker Compose V2 for test runner environment setup scripts * Fix - Reverts changes related to Direct Checkout that broke the PayPal extension. * Fix - Translate hardcoded strings on the Connect page * Update - refactor: separate BNPL methods from settings list @@ -36,6 +66,7 @@ * Dev - Match the Node version in nvm with the minimum version in package.json. * Dev - Remove unnecessary console.warn statements added in #9121. * Dev - Update bundle size checker workflow to support node v20 +* Dev - Migrate to Docker Compose V2 for test runner environment setup scripts = 8.0.2 - 2024-08-07 = * Fix - Add opt-in checks to prevent blocking customers using other payment methods. diff --git a/changelog/update-e2e-setup-script b/changelog/update-e2e-setup-script deleted file mode 100644 index 684cf9ced6d..00000000000 --- a/changelog/update-e2e-setup-script +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Define WooPay url for e2e env setup - - diff --git a/changelog/update-e2e-setup-script-env-var b/changelog/update-e2e-setup-script-env-var deleted file mode 100644 index 457978d1fae..00000000000 --- a/changelog/update-e2e-setup-script-env-var +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Define WooPay Blog ID for e2e env setup - - diff --git a/client/checkout/blocks/index.js b/client/checkout/blocks/index.js index e837734ac8c..1648598a24b 100644 --- a/client/checkout/blocks/index.js +++ b/client/checkout/blocks/index.js @@ -44,6 +44,7 @@ import { handleWooPayEmailInput } from '../woopay/email-input-iframe'; import { recordUserEvent } from 'tracks'; import wooPayExpressCheckoutPaymentMethod from '../woopay/express-button/woopay-express-checkout-payment-method'; import { isPreviewing } from '../preview'; +import '../utils/copy-test-number'; const upeMethods = { card: PAYMENT_METHOD_NAME_CARD, diff --git a/client/checkout/blocks/payment-elements.js b/client/checkout/blocks/payment-elements.js index 7bc9c3a1372..84ac4be9393 100644 --- a/client/checkout/blocks/payment-elements.js +++ b/client/checkout/blocks/payment-elements.js @@ -90,6 +90,7 @@ const PaymentElements = ( { api, ...props } ) => { errorMessage={ errorMessage } fingerprint={ fingerprint } onLoadError={ setPaymentProcessorLoadErrorMessage } + theme={ appearance?.theme } { ...props } /> diff --git a/client/checkout/blocks/payment-processor.js b/client/checkout/blocks/payment-processor.js index 88b72393524..c8ead3ad914 100644 --- a/client/checkout/blocks/payment-processor.js +++ b/client/checkout/blocks/payment-processor.js @@ -12,6 +12,7 @@ import { } from '@woocommerce/blocks-registry'; import { __ } from '@wordpress/i18n'; import { useEffect, useRef } from 'react'; +import classNames from 'classnames'; /** * Internal dependencies @@ -64,6 +65,7 @@ const PaymentProcessor = ( { shouldSavePayment, fingerprint, onLoadError = noop, + theme, } ) => { const stripe = useStripe(); const elements = useElements(); @@ -267,7 +269,9 @@ const PaymentProcessor = ( { <> { isTestMode && (

{ + copyNumberButton.classList.remove( 'state--success' ); + }, 2000 ); + }, + false +); diff --git a/client/checkout/utils/index.js b/client/checkout/utils/index.js index b0c14b7ffa5..de8ed29d773 100644 --- a/client/checkout/utils/index.js +++ b/client/checkout/utils/index.js @@ -20,7 +20,7 @@ export const getAppearanceType = () => { } if ( document.querySelector( '.woocommerce-billing-fields' ) ) { - return 'shortcode_checkout'; + return 'woopay_shortcode_checkout'; } if ( document.querySelector( '.wp-block-woocommerce-cart' ) ) { diff --git a/client/checkout/woopay/email-input-iframe.js b/client/checkout/woopay/email-input-iframe.js index 50ad4940d1d..4dfa1698fcd 100644 --- a/client/checkout/woopay/email-input-iframe.js +++ b/client/checkout/woopay/email-input-iframe.js @@ -6,8 +6,10 @@ import { getConfig } from 'wcpay/utils/checkout'; import { recordUserEvent, getTracksIdentity } from 'tracks'; import request from '../utils/request'; import { buildAjaxURL } from 'utils/express-checkout'; +import { getAppearance } from 'checkout/upe-styles'; import { getTargetElement, + getAppearanceType, validateEmail, appendRedirectionParams, shouldSkipWooPay, @@ -177,6 +179,7 @@ export const handleWooPayEmailInput = async ( iframe.addEventListener( 'load', () => { // Set the initial value. iframeHeaderValue = true; + const appearanceType = getAppearanceType(); if ( getConfig( 'isWoopayFirstPartyAuthEnabled' ) ) { request( @@ -186,6 +189,9 @@ export const handleWooPayEmailInput = async ( order_id: getConfig( 'order_id' ), key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), + appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) + ? getAppearance( appearanceType ) + : null, } ).then( ( response ) => { if ( response?.data?.session ) { diff --git a/client/checkout/woopay/express-button/express-checkout-iframe.js b/client/checkout/woopay/express-button/express-checkout-iframe.js index 1abe335763a..6685d237f49 100644 --- a/client/checkout/woopay/express-button/express-checkout-iframe.js +++ b/client/checkout/woopay/express-button/express-checkout-iframe.js @@ -285,5 +285,5 @@ export const expressCheckoutIframe = async ( api, context, emailSelector ) => { } } - openIframe( woopayEmailInput?.value ); + openIframe( woopayEmailInput?.value || getConfig( 'woopaySessionEmail' ) ); }; diff --git a/client/checkout/woopay/index.js b/client/checkout/woopay/index.js index a9766f4c4ee..cdc2ca864f3 100644 --- a/client/checkout/woopay/index.js +++ b/client/checkout/woopay/index.js @@ -31,7 +31,7 @@ const renderSaveUserSection = () => { )?.[ 0 ]; checkoutPageSaveUserContainer.className = - 'wc-block-checkout__payment-method wp-block-woocommerce-checkout-remember-block '; + 'wc-block-checkout__payment-method wp-block-woocommerce-checkout-remember-block wc-block-components-checkout-step '; checkoutPageSaveUserContainer.id = 'remember-me'; if ( paymentOptions ) { diff --git a/client/components/tooltip/style.scss b/client/components/tooltip/style.scss index 7e5d589c51a..51422218e10 100644 --- a/client/components/tooltip/style.scss +++ b/client/components/tooltip/style.scss @@ -56,7 +56,8 @@ text-align: center; a { - color: var( --wp-admin-theme-color, $gutenberg-blue ); + color: var( --wp-admin-theme-color-background-25, $wp-blue-5 ); + text-decoration: underline; } ul { diff --git a/client/components/woopay/save-user/checkout-page-save-user.js b/client/components/woopay/save-user/checkout-page-save-user.js index 4087255a68b..69cde030454 100644 --- a/client/components/woopay/save-user/checkout-page-save-user.js +++ b/client/components/woopay/save-user/checkout-page-save-user.js @@ -86,7 +86,7 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { rememberMe.removeAttribute( 'disabled', 'disabled' ); }, [ checkoutIsProcessing, isBlocksCheckout ] ); - const getPhoneFieldValue = () => { + const getPhoneFieldValue = useCallback( () => { let phoneFieldValue = ''; if ( isBlocksCheckout ) { phoneFieldValue = @@ -109,7 +109,7 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { } return phoneFieldValue; - }; + }, [ isBlocksCheckout ] ); const sendExtensionData = useCallback( ( shouldClearData = false ) => { @@ -172,7 +172,7 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { } ); }, [] ); - const updatePhoneNumberValidationError = useCallback( () => { + useEffect( () => { if ( ! isSaveDetailsChecked ) { clearValidationError( errorId ); if ( isPhoneValid !== null ) { @@ -218,6 +218,10 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { ? isWCPayChosen : isWCPayChosen && isNewPaymentTokenChosen; + useEffect( () => { + setPhoneNumber( getPhoneFieldValue() ); + }, [ getPhoneFieldValue, isWCPayWithNewTokenChosen ] ); + if ( ! getConfig( 'forceNetworkSavedCards' ) || ! isWCPayWithNewTokenChosen || @@ -232,8 +236,6 @@ const CheckoutPageSaveUser = ( { isBlocksCheckout } ) => { return null; } - updatePhoneNumberValidationError(); - return ( { ) } - + { __( 'Securely save my information for 1-click checkout', 'woocommerce-payments' diff --git a/client/connect-account-page/index.tsx b/client/connect-account-page/index.tsx index 5f7a383d827..ac1668de0a8 100644 --- a/client/connect-account-page/index.tsx +++ b/client/connect-account-page/index.tsx @@ -205,11 +205,6 @@ const ConnectAccountPage: React.FC = () => { setTestDriveModeSubmitted( true ); trackConnectAccountClicked( true ); - // Scroll the page to the top to ensure the logo is visible. - window.scrollTo( { - top: 0, - } ); - const customizedConnectUrl = addQueryArgs( connectUrl, { test_drive: 'true', } ); diff --git a/client/data/settings/hooks.js b/client/data/settings/hooks.js index 921a04693ea..a1ea500b84d 100644 --- a/client/data/settings/hooks.js +++ b/client/data/settings/hooks.js @@ -497,11 +497,6 @@ export const useWooPayShowIncompatibilityNotice = () => select( STORE_NAME ).getShowWooPayIncompatibilityNotice() ); -export const useExpressCheckoutShowIncompatibilityNotice = () => - useSelect( ( select ) => - select( STORE_NAME ).getShowExpressCheckoutIncompatibilityNotice() - ); - export const useStripeBilling = () => { const { updateIsStripeBillingEnabled } = useDispatch( STORE_NAME ); diff --git a/client/data/settings/selectors.js b/client/data/settings/selectors.js index 6855eea10b2..b8d059f8e41 100644 --- a/client/data/settings/selectors.js +++ b/client/data/settings/selectors.js @@ -257,13 +257,6 @@ export const getShowWooPayIncompatibilityNotice = ( state ) => { return getSettings( state ).show_woopay_incompatibility_notice || false; }; -export const getShowExpressCheckoutIncompatibilityNotice = ( state ) => { - return ( - getSettings( state ).show_express_checkout_incompatibility_notice || - false - ); -}; - export const getIsStripeBillingEnabled = ( state ) => { return getSettings( state ).is_stripe_billing_enabled || false; }; diff --git a/client/express-checkout/blocks/components/express-checkout-component.js b/client/express-checkout/blocks/components/express-checkout-component.js index 3a996220167..572e88a8e9e 100644 --- a/client/express-checkout/blocks/components/express-checkout-component.js +++ b/client/express-checkout/blocks/components/express-checkout-component.js @@ -40,11 +40,7 @@ const adjustButtonHeights = ( buttonOptions, expressPaymentMethod ) => { // Apple Pay has a nearly imperceptible height difference. We increase it by 1px here. if ( buttonOptions.buttonTheme.applePay === 'black' ) { if ( expressPaymentMethod === 'applePay' ) { - // The maximum allowed size is 55px. - buttonOptions.buttonHeight = Math.min( - buttonOptions.buttonHeight + 0.4, - 55 - ); + buttonOptions.buttonHeight = buttonOptions.buttonHeight + 0.4; } } @@ -56,6 +52,11 @@ const adjustButtonHeights = ( buttonOptions, expressPaymentMethod ) => { buttonOptions.buttonHeight = buttonOptions.buttonHeight - 2; } + // Clamp the button height to the allowed range 40px to 55px. + buttonOptions.buttonHeight = Math.max( + 40, + Math.min( buttonOptions.buttonHeight, 55 ) + ); return buttonOptions; }; diff --git a/client/express-checkout/blocks/express-checkout-element.scss b/client/express-checkout/blocks/express-checkout-element.scss index 17c3b5a9a4f..4b4e3d5e29e 100644 --- a/client/express-checkout/blocks/express-checkout-element.scss +++ b/client/express-checkout/blocks/express-checkout-element.scss @@ -15,13 +15,9 @@ height: 20px; } -// Checkout Block -.wc-block-components-express-payment--checkout { - .wc-block-components-express-payment__event-buttons { - grid-gap: 12px !important; - grid-template-columns: repeat( - auto-fit, - minmax( 232px, 1fr ) - ) !important; - } +.wc-block-components-express-payment + .wc-block-components-express-payment__event-buttons + > li { + margin-left: 1px !important; + width: 99% !important; } diff --git a/client/express-checkout/blocks/index.js b/client/express-checkout/blocks/index.js index 30dc6853111..b77bbb1fd69 100644 --- a/client/express-checkout/blocks/index.js +++ b/client/express-checkout/blocks/index.js @@ -6,6 +6,7 @@ import { getConfig } from 'wcpay/utils/checkout'; import ApplePayPreview from './components/apple-pay-preview'; import ExpressCheckoutContainer from './components/express-checkout-container'; import GooglePayPreview from './components/google-pay-preview'; +import { checkPaymentMethodIsAvailable } from '../utils/checkPaymentMethodIsAvailable'; const expressCheckoutElementApplePay = ( api ) => ( { paymentMethodId: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT, @@ -17,35 +18,41 @@ const expressCheckoutElementApplePay = ( api ) => ( { supports: { features: getConfig( 'features' ), }, - canMakePayment: () => { + canMakePayment: ( { cart } ) => { if ( typeof wcpayExpressCheckoutParams === 'undefined' ) { return false; } - return true; + return new Promise( ( resolve ) => { + checkPaymentMethodIsAvailable( 'applePay', cart, resolve ); + } ); }, } ); -const expressCheckoutElementGooglePay = ( api ) => ( { - paymentMethodId: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT, - name: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT + '_googlePay', - content: ( - - ), - edit: , - supports: { - features: getConfig( 'features' ), - }, - canMakePayment: () => { - if ( typeof wcpayExpressCheckoutParams === 'undefined' ) { - return false; - } +const expressCheckoutElementGooglePay = ( api ) => { + return { + paymentMethodId: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT, + name: PAYMENT_METHOD_NAME_EXPRESS_CHECKOUT_ELEMENT + '_googlePay', + content: ( + + ), + edit: , + supports: { + features: getConfig( 'features' ), + }, + canMakePayment: ( { cart } ) => { + if ( typeof wcpayExpressCheckoutParams === 'undefined' ) { + return false; + } - return true; - }, -} ); + return new Promise( ( resolve ) => { + checkPaymentMethodIsAvailable( 'googlePay', cart, resolve ); + } ); + }, + }; +}; export { expressCheckoutElementApplePay, expressCheckoutElementGooglePay }; diff --git a/client/express-checkout/index.js b/client/express-checkout/index.js index d0a5966ffcf..97eae8a9f97 100644 --- a/client/express-checkout/index.js +++ b/client/express-checkout/index.js @@ -210,10 +210,7 @@ jQuery( ( $ ) => { } return options.displayItems - .filter( - ( i ) => - i.label === __( 'Shipping', 'woocommerce-payments' ) - ) + .filter( ( i ) => i.key === 'total_shipping' ) .map( ( i ) => ( { id: `rate-${ i.label }`, amount: i.amount, @@ -228,7 +225,7 @@ jQuery( ( $ ) => { // Relying on what's provided in the cart response seems safest since it should always include a valid shipping // rate if one is required and available. // If no shipping rate is found we can't render the button so we just exit. - if ( options.requestShipping && ! shippingRates ) { + if ( options.requestShipping && ! shippingRates.length ) { return; } @@ -245,7 +242,17 @@ jQuery( ( $ ) => { getExpressCheckoutButtonStyleSettings() ); - wcpayECE.showButton( eceButton ); + wcpayECE.renderButton( eceButton ); + + eceButton.on( 'loaderror', () => { + wcPayECEError = __( + 'The cart is incompatible with express checkout.', + 'woocommerce-payments' + ); + if ( ! document.getElementById( 'wcpay-woopay-button' ) ) { + wcpayECE?.getButtonSeparator()?.hide(); + } + } ); eceButton.on( 'click', function ( event ) { // If login is required for checkout, display redirect confirmation dialog. @@ -329,7 +336,19 @@ jQuery( ( $ ) => { onCancelHandler(); } ); - eceButton.on( 'ready', onReadyHandler ); + eceButton.on( 'ready', ( onReadyParams ) => { + onReadyHandler( onReadyParams ); + + if ( + onReadyParams?.availablePaymentMethods && + Object.values( + onReadyParams.availablePaymentMethods + ).filter( Boolean ).length + ) { + wcpayECE.show(); + wcpayECE.getButtonSeparator().show(); + } + } ); if ( getExpressCheckoutData( 'is_product_page' ) ) { wcpayECE.attachProductPageEventListeners( elements ); @@ -523,18 +542,24 @@ jQuery( ( $ ) => { }, getElements: () => { - return $( - '.wcpay-payment-request-wrapper,#wcpay-express-checkout-button-separator' - ); + return $( '#wcpay-express-checkout-element' ); + }, + + getButtonSeparator: () => { + return $( '#wcpay-express-checkout-button-separator' ); }, show: () => { wcpayECE.getElements().show(); }, - showButton: ( eceButton ) => { + hide: () => { + wcpayECE.getElements().hide(); + wcpayECE.getButtonSeparator().hide(); + }, + + renderButton: ( eceButton ) => { if ( $( '#wcpay-express-checkout-element' ).length ) { - wcpayECE.show(); eceButton.mount( '#wcpay-express-checkout-element' ); } }, diff --git a/client/express-checkout/utils/checkPaymentMethodIsAvailable.js b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js new file mode 100644 index 00000000000..a42d7ffefe9 --- /dev/null +++ b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js @@ -0,0 +1,79 @@ +/** + * External dependencies + */ +import ReactDOM from 'react-dom'; +import { ExpressCheckoutElement, Elements } from '@stripe/react-stripe-js'; +import { memoize } from 'lodash'; + +/** + * Internal dependencies + */ +import { isLinkEnabled } from 'wcpay/checkout/utils/upe'; +import request from 'wcpay/checkout/utils/request'; +import WCPayAPI from 'wcpay/checkout/api'; +import { getUPEConfig } from 'wcpay/utils/checkout'; + +export const checkPaymentMethodIsAvailable = memoize( + ( paymentMethod, cart, resolve ) => { + const root = ReactDOM.createRoot( + document.getElementById( + `express-checkout-check-availability-container-${ paymentMethod }` + ) + ); + + const api = new WCPayAPI( + { + publishableKey: getUPEConfig( 'publishableKey' ), + accountId: getUPEConfig( 'accountId' ), + forceNetworkSavedCards: getUPEConfig( + 'forceNetworkSavedCards' + ), + locale: getUPEConfig( 'locale' ), + isStripeLinkEnabled: isLinkEnabled( + getUPEConfig( 'paymentMethodsConfig' ) + ), + }, + request + ); + + root.render( + + resolve( false ) } + options={ { + paymentMethods: { + amazonPay: 'never', + applePay: + paymentMethod === 'applePay' + ? 'always' + : 'never', + googlePay: + paymentMethod === 'googlePay' + ? 'always' + : 'never', + link: 'never', + paypal: 'never', + }, + } } + onReady={ ( event ) => { + let canMakePayment = false; + if ( event.availablePaymentMethods ) { + canMakePayment = + event.availablePaymentMethods[ paymentMethod ]; + } + resolve( canMakePayment ); + root.unmount(); + } } + /> + + ); + } +); diff --git a/client/express-checkout/utils/index.ts b/client/express-checkout/utils/index.ts index 8a21a75260c..28174431447 100644 --- a/client/express-checkout/utils/index.ts +++ b/client/express-checkout/utils/index.ts @@ -179,7 +179,7 @@ export const getExpressCheckoutButtonAppearance = () => { return { variables: { borderRadius: `${ - buttonSettings?.radius ?? getDefaultBorderRadius() + buttonSettings?.radius || getDefaultBorderRadius() }px`, spacingUnit: '6px', }, diff --git a/client/frontend-tracks/index.js b/client/frontend-tracks/index.js new file mode 100644 index 00000000000..33d13c3321b --- /dev/null +++ b/client/frontend-tracks/index.js @@ -0,0 +1,12 @@ +/** + * Internal dependencies + */ +import { recordUserEvent } from 'tracks'; + +if ( window.wcPayFrontendTracks && window.wcPayFrontendTracks.length ) { + for ( const track of window.wcPayFrontendTracks ) { + recordUserEvent( track.event, track.properties ); + } + + window.wcPayFrontendTracks = []; +} diff --git a/client/globals.d.ts b/client/globals.d.ts index 7284347d48e..44d94baa01b 100644 --- a/client/globals.d.ts +++ b/client/globals.d.ts @@ -16,6 +16,7 @@ declare global { paymentTimeline: boolean; isDisputeIssuerEvidenceEnabled: boolean; isPaymentOverviewWidgetEnabled?: boolean; + isEmbeddedKycEnabled?: boolean; }; fraudServices: unknown[]; testMode: boolean; @@ -185,4 +186,11 @@ declare global { homeUrl: string; siteTitle: string; }; + + interface Window { + wcpaySettings: typeof wcpaySettings; + wc: typeof wc; + wcTracks: typeof wcTracks; + wcSettings: typeof wcSettings; + } } diff --git a/client/index.js b/client/index.js index 9a530a54010..a5eb2d8376c 100644 --- a/client/index.js +++ b/client/index.js @@ -29,6 +29,7 @@ import CapitalPage from 'capital'; import OverviewPage from 'overview'; import DocumentsPage from 'documents'; import OnboardingPage from 'onboarding'; +import OnboardingKycPage from 'onboarding/kyc'; import FraudProtectionAdvancedSettingsPage from './settings/fraud-protection/advanced-settings'; import { getTasks } from 'overview/task-list/tasks'; @@ -69,6 +70,26 @@ addFilter( capability: 'manage_woocommerce', } ); + // Currently under feature flag. + if ( + wcpaySettings && + wcpaySettings.featureFlags.isEmbeddedKycEnabled + ) { + pages.push( { + container: OnboardingKycPage, + path: '/payments/onboarding/kyc', + wpOpenMenu: menuID, + breadcrumbs: [ + rootLink, + __( 'Continue onboarding', 'woocommerce-payments' ), + ], + navArgs: { + id: 'wc-payments-continue-onboarding', + }, + capability: 'manage_woocommerce', + } ); + } + pages.push( { container: OverviewPage, path: '/payments/overview', diff --git a/client/onboarding/index.tsx b/client/onboarding/index.tsx index 5def61ebd4b..1dbb237f7cd 100644 --- a/client/onboarding/index.tsx +++ b/client/onboarding/index.tsx @@ -13,11 +13,12 @@ import { getMccFromIndustry } from 'onboarding/utils'; import { OnboardingForm } from './form'; import Step from './step'; import BusinessDetails from './steps/business-details'; +import EmbeddedKyc from './steps/embedded-kyc'; import StoreDetails from './steps/store-details'; -import LoadingStep from './steps/loading'; import { trackStarted } from './tracking'; import { getAdminUrl } from 'wcpay/utils'; import './style.scss'; +import LoadingStep from 'wcpay/onboarding/steps/loading'; const OnboardingStepper = () => { const handleExit = () => { @@ -47,7 +48,13 @@ const OnboardingStepper = () => { - + { wcpaySettings?.featureFlags?.isEmbeddedKycEnabled ? ( + + + + ) : ( + + ) } ); }; @@ -78,10 +85,7 @@ const initialData = { const OnboardingPage: React.FC = () => { useEffect( () => { - const urlParams = new URLSearchParams( window.location.search ); - const source = - urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown'; - trackStarted( source ); + trackStarted(); // Remove loading class and add those required for full screen. document.body.classList.remove( 'woocommerce-admin-is-loading' ); diff --git a/client/onboarding/kyc/appearance.ts b/client/onboarding/kyc/appearance.ts new file mode 100644 index 00000000000..db0260c302f --- /dev/null +++ b/client/onboarding/kyc/appearance.ts @@ -0,0 +1,62 @@ +/* eslint-disable max-len */ + +/** + * Customised appearance variables for the external KYC flow. + */ +export default { + variables: { + colorPrimary: '#3C2861', + colorBackground: '#FFFFFF', + buttonPrimaryColorBackground: '#3858E9', + buttonPrimaryColorBorder: '#3858E9', + buttonPrimaryColorText: '#FFFFFF', + buttonSecondaryColorBackground: '#FFFFFF', + buttonSecondaryColorBorder: '#3858E9', + buttonSecondaryColorText: '#3858E9', + colorText: '#101517', + colorSecondaryText: '#50575E', + actionPrimaryColorText: '#3858E9', + actionSecondaryColorText: '#101517', + colorBorder: '#DDDDDD', + formHighlightColorBorder: '#3858E9', + formAccentColor: '#3858E9', + colorDanger: '#CC1818', + offsetBackgroundColor: '#F0F0F0', + formBackgroundColor: '#FFFFFF', + badgeNeutralColorText: '#2C3338', + badgeNeutralColorBackground: '#F6F7F7', + badgeNeutralColorBorder: '#F6F7F7', + badgeSuccessColorText: '#005C12', + badgeSuccessColorBackground: '#EDFAEF', + badgeSuccessColorBorder: '#EDFAEF', + badgeWarningColorText: '#614200', + badgeWarningColorBackground: '#FCF9E8', + badgeWarningColorBorder: '#FCF9E8', + badgeDangerColorText: '#8A2424', + badgeDangerColorBackground: '#FCF0F1', + badgeDangerColorBorder: '#FCF0F1', + borderRadius: '2px', + buttonBorderRadius: '2px', + formBorderRadius: '2px', + badgeBorderRadius: '2px', + overlayBorderRadius: '8px', + spacingUnit: '10px', + fontFamily: + "-apple-system, BlinkMacSystemFont, 'system-ui', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Roboto', 'Arial', sans-serif", + fontSizeBase: '16px', + headingXlFontSize: '32px', + headingXlFontWeight: '400', + headingLgFontSize: '24px', + headingLgFontWeight: '400', + headingMdFontSize: '20px', + headingMdFontWeight: '400', + headingSmFontSize: '16px', + headingSmFontWeight: '600', + headingXsFontSize: '12px', + headingXsFontWeight: '600', + bodyMdFontWeight: '400', + bodyMdFontSize: '16px', + bodySmFontSize: '14px', + bodySmFontWeight: '400', + }, +}; diff --git a/client/onboarding/kyc/index.tsx b/client/onboarding/kyc/index.tsx new file mode 100644 index 00000000000..d2e9a4b5140 --- /dev/null +++ b/client/onboarding/kyc/index.tsx @@ -0,0 +1,77 @@ +/** + * External dependencies + */ +import React, { useEffect } from 'react'; +import { closeSmall, Icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import Logo from 'assets/images/woopayments.svg'; +import Page from 'components/page'; +import { OnboardingContextProvider } from 'onboarding/context'; +import EmbeddedKyc from 'onboarding/steps/embedded-kyc'; +import strings from 'onboarding/strings'; +import { getConnectUrl } from 'utils'; + +const OnboardingKycPage: React.FC = () => { + const handleExit = () => { + const urlParams = new URLSearchParams( window.location.search ); + + window.location.href = getConnectUrl( + { + source: + urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || + 'unknown', + }, + 'WCPAY_ONBOARDING_KYC' + ); + }; + + useEffect( () => { + // Remove loading class and add those required for full screen. + document.body.classList.remove( 'woocommerce-admin-is-loading' ); + document.body.classList.add( 'woocommerce-admin-full-screen' ); + document.body.classList.add( 'is-wp-toolbar-disabled' ); + document.body.classList.add( 'wcpay-onboarding__body' ); + + // Remove full screen classes on unmount. + return () => { + document.body.classList.remove( 'woocommerce-admin-full-screen' ); + document.body.classList.remove( 'is-wp-toolbar-disabled' ); + document.body.classList.remove( 'wcpay-onboarding__body' ); + }; + }, [] ); + return ( + + +

+ + WooPayments + +
+
+
+ +
+
+ + + ); +}; +export default OnboardingKycPage; diff --git a/client/onboarding/step.tsx b/client/onboarding/step.tsx index c6f4aee0b17..e827a06c107 100644 --- a/client/onboarding/step.tsx +++ b/client/onboarding/step.tsx @@ -17,17 +17,14 @@ import './style.scss'; interface Props { name: OnboardingSteps; + showHeading?: boolean; } -const Step: React.FC< Props > = ( { name, children } ) => { +const Step: React.FC< Props > = ( { name, children, showHeading = true } ) => { const { trackAbandoned } = useTrackAbandoned(); const { prevStep, exit } = useStepperContext(); const handleExit = () => { - const urlParams = new URLSearchParams( window.location.search ); - const source = - urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown'; - - trackAbandoned( 'exit', source ); + trackAbandoned( 'exit' ); exit(); }; @@ -36,7 +33,9 @@ const Step: React.FC< Props > = ( { name, children } ) => {
-

- { strings.steps[ name ].heading } -

-

- { strings.steps[ name ].subheading } -

+ { showHeading && ( + <> +

+ { strings.steps[ name ].heading } +

+

+ { strings.steps[ name ].subheading } +

+ + ) }
{ children }
diff --git a/client/onboarding/steps/embedded-kyc.tsx b/client/onboarding/steps/embedded-kyc.tsx new file mode 100644 index 00000000000..68981261efd --- /dev/null +++ b/client/onboarding/steps/embedded-kyc.tsx @@ -0,0 +1,234 @@ +/** + * External dependencies + */ +import React, { useEffect, useState } from 'react'; +import { + loadConnectAndInitialize, + StripeConnectInstance, +} from '@stripe/connect-js'; +import { LoadError } from '@stripe/connect-js/types/config'; +import { + ConnectAccountOnboarding, + ConnectComponentsProvider, +} from '@stripe/react-connect-js'; +import apiFetch from '@wordpress/api-fetch'; +import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { NAMESPACE } from 'data/constants'; +import appearance from '../kyc/appearance'; +import BannerNotice from 'wcpay/components/banner-notice'; +import LoadBar from 'wcpay/components/load-bar'; +import { useOnboardingContext } from 'wcpay/onboarding/context'; +import { + AccountKycSession, + PoEligibleData, + PoEligibleResult, +} from 'wcpay/onboarding/types'; +import { fromDotNotation } from 'wcpay/onboarding/utils'; +import { getConnectUrl, getOverviewUrl } from 'wcpay/utils'; + +type AccountKycSessionData = AccountKycSession; + +interface FinalizeResponse { + success: boolean; + params: Record< string, string >; +} + +interface Props { + continueKyc?: boolean; +} + +const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => { + const { data } = useOnboardingContext(); + const [ publishableKey, setPublishableKey ] = useState( '' ); + const [ locale, setLocale ] = useState( '' ); + const [ clientSecret, setClientSecret ] = useState< + ( () => Promise< string > ) | null + >( null ); + const [ + stripeConnectInstance, + setStripeConnectInstance, + ] = useState< StripeConnectInstance | null >( null ); + const [ loading, setLoading ] = useState( true ); + const [ loadErrorMessage, setLoadErrorMessage ] = useState( '' ); + const onLoaderStart = () => { + setLoading( false ); + }; + const onLoadError = ( loadError: LoadError ) => { + setLoadErrorMessage( loadError.error.message || 'Unknown error' ); + }; + + useEffect( () => { + const isEligibleForPo = async () => { + if ( + ! data.country || + ! data.business_type || + ! data.mcc || + ! data.annual_revenue || + ! data.go_live_timeframe + ) { + return false; + } + const eligibilityDetails: PoEligibleData = { + business: { + country: data.country, + type: data.business_type, + mcc: data.mcc, + }, + store: { + annual_revenue: data.annual_revenue, + go_live_timeframe: data.go_live_timeframe, + }, + }; + + try { + const eligibleResult = await apiFetch< PoEligibleResult >( { + path: '/wc/v3/payments/onboarding/router/po_eligible', + method: 'POST', + data: eligibilityDetails, + } ); + + return 'eligible' === eligibleResult.result; + } catch ( error ) { + // Fall back to full KYC scenario. + return false; + } + }; + + const fetchKeys = async () => { + // By default, we assume the merchant is not eligible for PO. + let isEligible = false; + + // If we are resuming an onboarding session, we don't need to check for PO eligibility again. + if ( ! continueKyc ) { + isEligible = await isEligibleForPo(); + } + + const path = addQueryArgs( + `${ NAMESPACE }/onboarding/kyc/session`, + { + self_assessment: fromDotNotation( data ), + progressive: isEligible, + } + ); + const accountSession = await apiFetch< AccountKycSessionData >( { + path: path, + method: 'GET', + } ); + if ( + accountSession.publishableKey && + accountSession.clientSecret + ) { + setPublishableKey( accountSession.publishableKey ); + setLocale( accountSession.locale ); + setClientSecret( () => () => + Promise.resolve( accountSession.clientSecret ) + ); // Ensure clientSecret is wrapped as a function returning a Promise + } else { + setLoading( false ); + setLoadErrorMessage( + __( + "Failed to create account session. Please check that you're using the latest version of WooPayments.", + 'woocommerce-payments' + ) + ); + } + }; + + fetchKeys(); + }, [ data, continueKyc ] ); + + // Initialize the Stripe Connect instance only once when publishableKey and clientSecret are ready + useEffect( () => { + if ( publishableKey && clientSecret && ! stripeConnectInstance ) { + const stripeInstance = loadConnectAndInitialize( { + publishableKey: publishableKey, + fetchClientSecret: clientSecret, // Pass the function returning the Promise + appearance: { + // See all possible variables below + overlays: 'drawer', + variables: appearance.variables, + }, + locale: locale.replace( '_', '-' ), + } ); + + setStripeConnectInstance( stripeInstance ); + } + }, [ publishableKey, clientSecret, stripeConnectInstance, locale ] ); + + return ( + <> + { loading && } + { loadErrorMessage && ( + { loadErrorMessage } + ) } + { stripeConnectInstance && ( + + { + const urlParams = new URLSearchParams( + window.location.search + ); + const urlSource = + urlParams + .get( 'source' ) + ?.replace( /[^\w-]+/g, '' ) || 'unknown'; + try { + const response = await apiFetch< + FinalizeResponse + >( { + path: `${ NAMESPACE }/onboarding/kyc/finalize`, + method: 'POST', + data: { + source: urlSource, + from: 'WCPAY_ONBOARDING_WIZARD', + clientSecret: clientSecret, + }, + } ); + + if ( response.success ) { + window.location.href = getOverviewUrl( + { + ...response.params, + 'wcpay-connection-success': '1', + }, + 'WCPAY_ONBOARDING_WIZARD' + ); + } else { + // If a non-success response is received we should redirect to the Connect page with an error flag: + window.location.href = getConnectUrl( + { + ...response.params, + 'wcpay-connection-error': '1', + }, + 'WCPAY_ONBOARDING_WIZARD' + ); + } + } catch ( error ) { + // If an error response is received we should redirect to the Connect page with an error flag: + // Note that this should never happen, since we always expect a response from the server. + window.location.href = getConnectUrl( + { + 'wcpay-connection-error': '1', + source: urlSource, + }, + 'WCPAY_ONBOARDING_WIZARD' + ); + } + } } + /> + + ) } + + ); +}; + +export default EmbeddedKyc; diff --git a/client/onboarding/steps/loading.tsx b/client/onboarding/steps/loading.tsx index 297bf5fb799..c7b08dc905b 100644 --- a/client/onboarding/steps/loading.tsx +++ b/client/onboarding/steps/loading.tsx @@ -57,26 +57,26 @@ const LoadingStep: React.FC< Props > = () => { const handleComplete = async () => { const { connectUrl } = wcpaySettings; - const urlParams = new URLSearchParams( window.location.search ); - const urlSource = - urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown'; - - let isEligible; + let isPoEligible; try { - isEligible = await isEligibleForPo(); + isPoEligible = await isEligibleForPo(); } catch ( error ) { // fall back to full KYC scenario. // TODO maybe log these errors in future, e.g. with tracks. - isEligible = false; + isPoEligible = false; } - trackRedirected( isEligible, urlSource ); + trackRedirected( isPoEligible ); removeTrackListener(); + const urlParams = new URLSearchParams( window.location.search ); + window.location.href = addQueryArgs( connectUrl, { self_assessment: fromDotNotation( data ), - progressive: isEligible, - source: urlSource, + progressive: isPoEligible, + source: + urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || + 'unknown', from: 'WCPAY_ONBOARDING_WIZARD', } ); }; diff --git a/client/onboarding/steps/test/embedded-onboarding.tsx b/client/onboarding/steps/test/embedded-onboarding.tsx new file mode 100644 index 00000000000..bd3ee34b1bd --- /dev/null +++ b/client/onboarding/steps/test/embedded-onboarding.tsx @@ -0,0 +1,93 @@ +/** + * External dependencies + */ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import apiFetch from '@wordpress/api-fetch'; +import { loadConnectAndInitialize } from '@stripe/connect-js'; + +/** + * Internal dependencies + */ +import EmbeddedKyc from '../embedded-kyc'; + +jest.mock( '@wordpress/api-fetch' ); +jest.mock( '@stripe/connect-js', () => ( { + loadConnectAndInitialize: jest.fn(), +} ) ); + +// Mock data, setData from OnboardingContext +const data = { + country: 'US', + business_type: 'individual', + mcc: 'most_popular__software_services', + annual_revenue: 'less_than_250k', + go_live_timeframe: 'within_1month', +}; +const setData = jest.fn(); + +jest.mock( '../../context', () => ( { + useOnboardingContext: jest.fn( () => ( { + data, + setData, + } ) ), +} ) ); + +jest.mock( 'components/stepper', () => ( { + useStepperContext: jest.fn( () => ( { + currentStep: 'loading', + } ) ), +} ) ); + +describe( 'EmbeddedOnboarding', () => { + beforeEach( () => { + jest.clearAllMocks(); // Clear all mocks between tests + } ); + + it( 'should show error if account session fails', async () => { + jest.mocked( apiFetch ).mockResolvedValueOnce( { + result: 'eligible', + data: [], + } ); + + jest.mocked( apiFetch ).mockResolvedValueOnce( { + success: false, + } ); + + render( ); + + await waitFor( () => + expect( + screen.getByText( /Failed to create account session/i ) + ).toBeInTheDocument() + ); + } ); + + it( 'should initialize Stripe Connect when publishableKey and clientSecret are set', async () => { + jest.mocked( apiFetch ).mockResolvedValueOnce( { + result: 'eligible', + data: [], + } ); + + const mockAccountSessionData = { + publishableKey: 'test_publishable_key', + clientSecret: 'test_client_secret', + locale: 'en_US', + }; + jest.mocked( apiFetch ).mockResolvedValueOnce( mockAccountSessionData ); + + render( ); + + await waitFor( () => + expect( loadConnectAndInitialize ).toHaveBeenCalledWith( { + publishableKey: mockAccountSessionData.publishableKey, + fetchClientSecret: expect.any( Function ), + appearance: { + overlays: 'drawer', + variables: expect.any( Object ), + }, + locale: 'en-US', // Locale should be formatted correctly + } ) + ); + } ); +} ); diff --git a/client/onboarding/strings.tsx b/client/onboarding/strings.tsx index 02ea32d2998..432e4aae21d 100644 --- a/client/onboarding/strings.tsx +++ b/client/onboarding/strings.tsx @@ -44,6 +44,16 @@ export default { 'woocommerce-payments' ), }, + embedded: { + heading: __( + 'One last step! Verify your identity with our partner', + 'woocommerce-payments' + ), + subheading: __( + 'This info will verify your account', + 'woocommerce-payments' + ), + }, }, fields: { country: __( diff --git a/client/onboarding/style.scss b/client/onboarding/style.scss index 7a5efb46b0c..eb0c84771f6 100644 --- a/client/onboarding/style.scss +++ b/client/onboarding/style.scss @@ -36,6 +36,10 @@ body.wcpay-onboarding__body { &:last-child { justify-self: end; } + + &.hide { + visibility: hidden; + } } &-logo { diff --git a/client/onboarding/tracking.ts b/client/onboarding/tracking.ts index c46997b0b36..e4d9ec557f7 100644 --- a/client/onboarding/tracking.ts +++ b/client/onboarding/tracking.ts @@ -23,11 +23,15 @@ const stepElapsed = () => { return result; }; -export const trackStarted = ( source: string ): void => { +export const trackStarted = (): void => { + // Initialize the elapsed time tracking startTime = stepStartTime = Date.now(); + const urlParams = new URLSearchParams( window.location.search ); + recordEvent( 'wcpay_onboarding_flow_started', { - source, + source: + urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown', } ); }; @@ -42,14 +46,14 @@ export const trackStepCompleted = ( step: string ): void => { trackedSteps.add( step ); }; -export const trackRedirected = ( - isEligible: boolean, - source: string -): void => { +export const trackRedirected = ( isPoEligible: boolean ): void => { + const urlParams = new URLSearchParams( window.location.search ); + recordEvent( 'wcpay_onboarding_flow_redirected', { - is_po_eligible: isEligible, + is_po_eligible: isPoEligible, elapsed: elapsed( startTime ), - source, + source: + urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown', } ); }; @@ -66,13 +70,13 @@ export const trackEligibilityModalClosed = ( } ); export const useTrackAbandoned = (): { - trackAbandoned: ( method: 'hide' | 'exit', source: string ) => void; + trackAbandoned: ( method: 'hide' | 'exit' ) => void; removeTrackListener: () => void; } => { const { errors, touched } = useOnboardingContext(); const { currentStep: step } = useStepperContext(); - const trackEvent = ( method = 'hide', source = 'unknown' ) => { + const trackEvent = ( method = 'hide' ) => { const event = method === 'hide' ? 'wcpay_onboarding_flow_hidden' @@ -81,21 +85,21 @@ export const useTrackAbandoned = (): { ( field ) => touched[ field as keyof OnboardingFields ] ); + const urlParams = new URLSearchParams( window.location.search ); + recordEvent( event, { step, errored, elapsed: elapsed( startTime ), - source, + source: + urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || + 'unknown', } ); }; const listener = () => { if ( document.visibilityState === 'hidden' ) { - const urlParams = new URLSearchParams( window.location.search ); - const source = - urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || - 'unknown'; - trackEvent( 'hide', source ); + trackEvent( 'hide' ); } }; @@ -108,8 +112,8 @@ export const useTrackAbandoned = (): { }, [ step, errors, touched ] ); return { - trackAbandoned: ( method: string, source = 'unknown' ) => { - trackEvent( method, source ); + trackAbandoned: ( method: string ) => { + trackEvent( method ); document.removeEventListener( 'visibilitychange', listener ); }, removeTrackListener: () => diff --git a/client/onboarding/types.ts b/client/onboarding/types.ts index 2f57aa08e94..c2781390524 100644 --- a/client/onboarding/types.ts +++ b/client/onboarding/types.ts @@ -2,7 +2,7 @@ * Internal dependencies */ -export type OnboardingSteps = 'business' | 'store' | 'loading'; +export type OnboardingSteps = 'business' | 'store' | 'embedded' | 'loading'; export type OnboardingFields = { country?: string; @@ -13,6 +13,15 @@ export type OnboardingFields = { go_live_timeframe?: string; }; +export interface OnboardingProps { + country?: string; + type?: string; + structure?: string; + mcc?: string; + annual_revenue?: string; + go_live_timeframe?: string; +} + export interface PoEligibleResult { result: 'eligible' | 'not_eligible'; } @@ -55,3 +64,13 @@ export interface MccsDisplayTreeItem { mcc?: number; keywords?: string[]; } + +export interface AccountKycSession { + clientSecret: string; + expiresAt: number; + accountId: string; + isLive: boolean; + accountCreated: boolean; + publishableKey: string; + locale: string; +} diff --git a/client/overview/index.js b/client/overview/index.js index 5a2cc84ba52..f6d5afd4a72 100644 --- a/client/overview/index.js +++ b/client/overview/index.js @@ -190,6 +190,7 @@ const OverviewPage = () => { + { showConnectionSuccess && } { ! accountRejected && ! accountUnderReview && ( diff --git a/client/payment-methods-map.tsx b/client/payment-methods-map.tsx index 10b96f5d527..b22ca1f9ae2 100644 --- a/client/payment-methods-map.tsx +++ b/client/payment-methods-map.tsx @@ -24,16 +24,6 @@ import { SofortIcon, } from 'wcpay/payment-methods-icons'; -declare global { - interface Window { - wcpaySettings: { - accountStatus: { - country: string; - }; - }; - } -} - const accountCountry = window.wcpaySettings?.accountStatus?.country || 'US'; export interface PaymentMethodMapEntry { diff --git a/client/product-details/bnpl-site-messaging/index.js b/client/product-details/bnpl-site-messaging/index.js index 91be16744ab..36cb845fb71 100644 --- a/client/product-details/bnpl-site-messaging/index.js +++ b/client/product-details/bnpl-site-messaging/index.js @@ -8,14 +8,6 @@ import { getAppearance, getFontRulesFromPage } from 'wcpay/checkout/upe-styles'; import { getUPEConfig } from 'wcpay/utils/checkout'; import apiRequest from 'wcpay/checkout/utils/request'; -/** - * Initializes the appearance of the payment element by retrieving the UPE configuration - * from the API and saving the appearance if it doesn't exist. If the appearance already exists, - * it is simply returned. - * - * @param {Object} api The API object used to save the UPE configuration. - * @return {Promise} The appearance object for the UPE. - */ const elementsLocations = { bnplProductPage: { configKey: 'upeBnplProductPageAppearance', @@ -27,6 +19,16 @@ const elementsLocations = { }, }; +/** + * Initializes the appearance of the payment element by retrieving the UPE configuration + * from the API and saving the appearance if it doesn't exist. If the appearance already exists, + * it is simply returned. + * + * @param {Object} api The API object used to save the UPE configuration. + * @param {string} location The location of the UPE. + * + * @return {Promise} The appearance object for the UPE. + */ async function initializeAppearance( api, location ) { const { configKey, appearanceKey } = elementsLocations[ location ]; @@ -53,16 +55,25 @@ export const initializeBnplSiteMessaging = async () => { isCart, isCartBlock, cartTotal, + minimumOrderAmount, } = window.wcpayStripeSiteMessaging; let amount; let elementLocation = 'bnplProductPage'; + const minOrderAmount = parseInt( minimumOrderAmount, 10 ) || 0; + const paymentMessageContainer = document.getElementById( + 'payment-method-message' + ); if ( isCart || isCartBlock ) { amount = parseInt( cartTotal, 10 ) || 0; elementLocation = 'bnplClassicCart'; } else { amount = parseInt( productVariations.base_product.amount, 10 ) || 0; + + if ( amount < minOrderAmount ) { + paymentMessageContainer.style.setProperty( 'display', 'none' ); + } } let paymentMessageElement; @@ -140,9 +151,6 @@ export const initializeBnplSiteMessaging = async () => { } // Set the `--wc-bnpl-margin-bottom` CSS variable to the computed bottom margin of the price element. - const paymentMessageContainer = document.getElementById( - 'payment-method-message' - ); paymentMessageContainer.style.setProperty( '--wc-bnpl-margin-bottom', bottomMargin @@ -208,7 +216,7 @@ export const initializeBnplSiteMessaging = async () => { pmme.style.setProperty( '--wc-bnpl-margin-bottom', '-4px' ); }, 2000 ); } else { - paymentMessageLoading.remove(); + paymentMessageLoading?.remove(); } } ); } diff --git a/client/settings/express-checkout-settings/general-payment-request-button-settings.js b/client/settings/express-checkout-settings/general-payment-request-button-settings.js index 9154cf7fcde..296a09b08b0 100644 --- a/client/settings/express-checkout-settings/general-payment-request-button-settings.js +++ b/client/settings/express-checkout-settings/general-payment-request-button-settings.js @@ -7,7 +7,6 @@ import { __, sprintf } from '@wordpress/i18n'; import { // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalNumberControl as NumberControl, - CheckboxControl, SelectControl, RadioControl, RangeControl, @@ -32,7 +31,6 @@ import { usePaymentRequestButtonBorderRadius, usePaymentRequestEnabledSettings, useWooPayEnabledSettings, - useWooPayGlobalThemeSupportEnabledSettings, } from 'wcpay/data'; const makeButtonSizeText = ( string ) => @@ -167,11 +165,6 @@ const GeneralPaymentRequestButtonSettings = ( { type } ) => { isPaymentRequestEnabled && isWooPayFeatureFlagEnabled; - const [ - isWooPayGlobalThemeSupportEnabled, - updateIsWooPayGlobalThemeSupportEnabled, - ] = useWooPayGlobalThemeSupportEnabledSettings(); - return ( { showWarning && ( @@ -267,30 +260,6 @@ const GeneralPaymentRequestButtonSettings = ( { type } ) => {

) } - { wcpaySettings.isWooPayGlobalThemeSupportEligible && - type === 'woopay' && ( - <> -

- { __( - 'WooPay Global Theme Support', - 'woocommerce-payments' - ) } -

-
- -
- - ) }

{ __( 'Preview', 'woocommerce-payments' ) }

{ __( diff --git a/client/settings/express-checkout-settings/payment-request-settings.js b/client/settings/express-checkout-settings/payment-request-settings.js index 3ea6a055c64..a676eba6bbe 100644 --- a/client/settings/express-checkout-settings/payment-request-settings.js +++ b/client/settings/express-checkout-settings/payment-request-settings.js @@ -14,9 +14,7 @@ import GeneralPaymentRequestButtonSettings from './general-payment-request-butto import { usePaymentRequestEnabledSettings, usePaymentRequestLocations, - useExpressCheckoutShowIncompatibilityNotice, } from 'wcpay/data'; -import { ExpressCheckoutIncompatibilityNotice } from 'wcpay/settings/settings-warnings/incompatibility-notice'; const PaymentRequestSettings = ( { section } ) => { const [ @@ -42,15 +40,10 @@ const PaymentRequestSettings = ( { section } ) => { } }; - const showIncompatibilityNotice = useExpressCheckoutShowIncompatibilityNotice(); - return ( { section === 'enable' && ( - { showIncompatibilityNotice && ( - - ) } ( { .fn() .mockReturnValue( [ [ true, true, true ], jest.fn() ] ), useWooPayShowIncompatibilityNotice: jest.fn().mockReturnValue( false ), - useExpressCheckoutShowIncompatibilityNotice: jest - .fn() - .mockReturnValue( false ), } ) ); jest.mock( '@wordpress/data', () => ( { diff --git a/client/settings/express-checkout-settings/test/payment-request-settings.test.js b/client/settings/express-checkout-settings/test/payment-request-settings.test.js index b3746b1964c..4bc3a0b6e12 100644 --- a/client/settings/express-checkout-settings/test/payment-request-settings.test.js +++ b/client/settings/express-checkout-settings/test/payment-request-settings.test.js @@ -18,7 +18,6 @@ import { usePaymentRequestButtonSize, usePaymentRequestButtonTheme, useWooPayEnabledSettings, - useExpressCheckoutShowIncompatibilityNotice, } from '../../../data'; jest.mock( '../../../data', () => ( { @@ -29,11 +28,7 @@ jest.mock( '../../../data', () => ( { usePaymentRequestButtonSize: jest.fn().mockReturnValue( [ 'small' ] ), usePaymentRequestButtonTheme: jest.fn().mockReturnValue( [ 'dark' ] ), useWooPayEnabledSettings: jest.fn(), - useExpressCheckoutShowIncompatibilityNotice: jest.fn(), useWooPayShowIncompatibilityNotice: jest.fn().mockReturnValue( false ), - useWooPayGlobalThemeSupportEnabledSettings: jest - .fn() - .mockReturnValue( [ false, jest.fn() ] ), } ) ); jest.mock( '../payment-request-button-preview' ); @@ -260,28 +255,4 @@ describe( 'PaymentRequestSettings', () => { updatePaymentRequestLocationsHandler ).toHaveBeenLastCalledWith( [ 'checkout', 'product' ] ); } ); - - it( 'triggers the hooks when the enable setting is being interacted with', () => { - useExpressCheckoutShowIncompatibilityNotice.mockReturnValue( true ); - - render( ); - - expect( - screen.queryByText( - 'Your custom checkout fields may not be compatible with these payment methods.' - ) - ).toBeInTheDocument(); - } ); - - it( 'triggers the hooks when the enable setting is being interacted with', () => { - useExpressCheckoutShowIncompatibilityNotice.mockReturnValue( false ); - - render( ); - - expect( - screen.queryByText( - 'Your custom checkout fields may not be compatible with these payment methods.' - ) - ).not.toBeInTheDocument(); - } ); } ); diff --git a/client/settings/express-checkout-settings/test/woopay-settings.test.js b/client/settings/express-checkout-settings/test/woopay-settings.test.js index 5846e397ad6..2fcab112599 100644 --- a/client/settings/express-checkout-settings/test/woopay-settings.test.js +++ b/client/settings/express-checkout-settings/test/woopay-settings.test.js @@ -30,6 +30,9 @@ jest.mock( '../../../data', () => ( { usePaymentRequestButtonTheme: jest.fn(), useWooPayLocations: jest.fn(), useWooPayShowIncompatibilityNotice: jest.fn().mockReturnValue( false ), + useWooPayGlobalThemeSupportEnabledSettings: jest + .fn() + .mockReturnValue( [ false, jest.fn() ] ), } ) ); jest.mock( '@wordpress/data', () => ( { diff --git a/client/settings/express-checkout-settings/woopay-settings.js b/client/settings/express-checkout-settings/woopay-settings.js index 1cf1c72ea31..aa73506dde1 100644 --- a/client/settings/express-checkout-settings/woopay-settings.js +++ b/client/settings/express-checkout-settings/woopay-settings.js @@ -26,6 +26,7 @@ import { useWooPayStoreLogo, useWooPayLocations, useWooPayShowIncompatibilityNotice, + useWooPayGlobalThemeSupportEnabledSettings, } from 'wcpay/data'; import GeneralPaymentRequestButtonSettings from './general-payment-request-button-settings'; import { WooPayIncompatibilityNotice } from '../settings-warnings/incompatibility-notice'; @@ -41,6 +42,11 @@ const WooPaySettings = ( { section } ) => { setWooPayCustomMessage, ] = useWooPayCustomMessage(); + const [ + isWooPayGlobalThemeSupportEnabled, + updateIsWooPayGlobalThemeSupportEnabled, + ] = useWooPayGlobalThemeSupportEnabledSettings(); + const [ woopayStoreLogo, setWooPayStoreLogo ] = useWooPayStoreLogo(); const [ woopayLocations, updateWooPayLocations ] = useWooPayLocations(); @@ -203,6 +209,31 @@ const WooPaySettings = ( { section } ) => { updateFileID={ setWooPayStoreLogo } />
+ { wcpaySettings.isWooPayGlobalThemeSupportEligible && ( +
+

+ { __( + 'WooPay Global Theme Support', + 'woocommerce-payments' + ) } +

+
+ +
+
+ ) }

{ __( diff --git a/client/settings/express-checkout/apple-google-pay-item.tsx b/client/settings/express-checkout/apple-google-pay-item.tsx index a43af822015..3b1471fece8 100644 --- a/client/settings/express-checkout/apple-google-pay-item.tsx +++ b/client/settings/express-checkout/apple-google-pay-item.tsx @@ -10,13 +10,9 @@ import React, { useContext } from 'react'; * Internal dependencies */ import { getPaymentMethodSettingsUrl } from '../../utils'; -import { - usePaymentRequestEnabledSettings, - useExpressCheckoutShowIncompatibilityNotice, -} from 'wcpay/data'; +import { usePaymentRequestEnabledSettings } from 'wcpay/data'; import { PaymentRequestEnabledSettingsHook } from './interfaces'; import { ApplePayIcon, GooglePayIcon } from 'wcpay/payment-methods-icons'; -import { ExpressCheckoutIncompatibilityNotice } from 'wcpay/settings/settings-warnings/incompatibility-notice'; import DuplicateNotice from 'wcpay/components/duplicate-notice'; import DuplicatedPaymentMethodsContext from '../settings-manager/duplicated-payment-methods-context'; @@ -28,7 +24,6 @@ const AppleGooglePayExpressCheckoutItem = (): React.ReactElement => { updateIsPaymentRequestEnabled, ] = usePaymentRequestEnabledSettings() as PaymentRequestEnabledSettingsHook; - const showIncompatibilityNotice = useExpressCheckoutShowIncompatibilityNotice(); const { duplicates, dismissedDuplicateNotices, @@ -176,9 +171,6 @@ const AppleGooglePayExpressCheckoutItem = (): React.ReactElement => {

- { showIncompatibilityNotice && ( - - ) } { isDuplicate && ( ( { useEnabledPaymentMethodIds: jest.fn(), useGetAvailablePaymentMethodIds: jest.fn(), useWooPayShowIncompatibilityNotice: jest.fn(), - useExpressCheckoutShowIncompatibilityNotice: jest.fn(), useGetDuplicatedPaymentMethodIds: jest.fn(), } ) ); @@ -220,24 +218,4 @@ describe( 'ExpressCheckout', () => { ) ).toBeInTheDocument(); } ); - - it( 'should show Express Checkout incompatibility warning', async () => { - const context = { featureFlags: { woopay: true } }; - useGetAvailablePaymentMethodIds.mockReturnValue( [ 'link', 'card' ] ); - useEnabledPaymentMethodIds.mockReturnValue( [ [ 'card', 'link' ] ] ); - - useExpressCheckoutShowIncompatibilityNotice.mockReturnValue( true ); - - render( - - - - ); - - expect( - screen.queryByText( - 'Your custom checkout fields may not be compatible with these payment methods.' - ) - ).toBeInTheDocument(); - } ); } ); diff --git a/client/settings/payment-methods-list/payment-method.scss b/client/settings/payment-methods-list/payment-method.scss index a020b7562cb..b540af1bb31 100644 --- a/client/settings/payment-methods-list/payment-method.scss +++ b/client/settings/payment-methods-list/payment-method.scss @@ -27,17 +27,16 @@ flex: 1 1 100%; order: 1; display: flex; - flex-wrap: nowrap; justify-content: space-between; + flex-direction: column; @include breakpoint( '>660px' ) { + flex-wrap: nowrap; + flex-direction: row; + align-items: center; flex: 1 1 auto; order: 3; } - - @include breakpoint( '<660px' ) { - flex-wrap: wrap; - } } &__checkbox { @@ -158,13 +157,12 @@ &__fees { display: flex; - align-items: center; - justify-content: flex-end; white-space: nowrap; - margin-left: 16px; - @include breakpoint( '<660px' ) { - margin-left: 0; + @include breakpoint( '>660px' ) { + justify-content: flex-end; + margin-left: 16px; + flex-shrink: 0; } } diff --git a/client/settings/settings-warnings/incompatibility-notice.js b/client/settings/settings-warnings/incompatibility-notice.js index c3a388f8df6..bd69324b11a 100644 --- a/client/settings/settings-warnings/incompatibility-notice.js +++ b/client/settings/settings-warnings/incompatibility-notice.js @@ -40,14 +40,3 @@ export const WooPayIncompatibilityNotice = () => ( learnMoreLinkHref="https://woocommerce.com/document/woopay-merchant-documentation/#compatibility" /> ); - -export const ExpressCheckoutIncompatibilityNotice = () => ( - -); diff --git a/client/tracks/index.ts b/client/tracks/index.ts index d3f14dd57b3..5fd10f9c5e8 100644 --- a/client/tracks/index.ts +++ b/client/tracks/index.ts @@ -81,7 +81,7 @@ export const recordUserEvent = ( fetch( ajaxUrl, { method: 'post', body, - } ); + } ).then( ( response ) => response.json() ); }; /** @@ -125,6 +125,7 @@ export const getTracksIdentity = async (): Promise< string | undefined > => { body.append( 'tracksNonce', nonce ); body.append( 'action', 'get_identity' ); + try { const response = await fetch( ajaxUrl, { method: 'post', diff --git a/client/utils/account-fees.tsx b/client/utils/account-fees.tsx index 86ca513984d..b3d0f5b9f2c 100644 --- a/client/utils/account-fees.tsx +++ b/client/utils/account-fees.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { BaseFee, DiscountFee, FeeStructure } from 'wcpay/types/fees'; import { PaymentMethod } from 'wcpay/types/payment-methods'; import { createInterpolateElement } from '@wordpress/element'; +import { ExternalLink } from '@wordpress/components'; const countryFeeStripeDocsBaseLink = 'https://woocommerce.com/document/woopayments/fees-and-debits/fees/#'; @@ -180,7 +181,7 @@ export const formatMethodFeesTooltip = ( { wcpaySettings && wcpaySettings.connect && wcpaySettings.connect.country ? ( -
+
{ stripeFeeSectionExistsForCountry( wcpaySettings.connect.country @@ -196,19 +197,17 @@ export const formatMethodFeesTooltip = ( ), components: { linkToStripePage: ( - { __( 'Learn more', 'woocommerce-payments' ) } - + ), }, } ) @@ -223,18 +222,16 @@ export const formatMethodFeesTooltip = ( ), components: { linkToStripePage: ( - { __( 'Learn more', 'woocommerce-payments' ) } - + ), }, } ) } diff --git a/client/utils/express-checkout/index.js b/client/utils/express-checkout/index.js index c47ef82f490..bacc56c7f7e 100644 --- a/client/utils/express-checkout/index.js +++ b/client/utils/express-checkout/index.js @@ -24,7 +24,7 @@ export const getExpressCheckoutConfig = ( key ) => { export const getDefaultBorderRadius = () => { return parseInt( - wcpaySettings?.defaultExpressCheckoutBorderRadius ?? 4, + window?.wcpaySettings?.defaultExpressCheckoutBorderRadius || 4, 10 ); }; diff --git a/client/utils/index.js b/client/utils/index.js index bc24bf44f4f..c642c1034e1 100644 --- a/client/utils/index.js +++ b/client/utils/index.js @@ -100,6 +100,40 @@ export const getDocumentUrl = ( documentId ) => { ); }; +export const getConnectUrl = ( urlParams, from ) => { + // Ensure urlParams is an object. + const queryParams = typeof urlParams === 'object' ? urlParams : {}; + + const baseParams = { + page: 'wc-admin', + path: '/payments/connect', + source: queryParams.source?.replace( /[^\w-]+/g, '' ) || 'unknown', + from: from, + }; + + // Merge queryParams and baseParams into baseParams, ensuring baseParams takes precedence. + const params = { ...queryParams, ...baseParams }; + + return getAdminUrl( params ); +}; + +export const getOverviewUrl = ( urlParams, from ) => { + // Ensure urlParams is an object. + const queryParams = typeof urlParams === 'object' ? urlParams : {}; + + const baseParams = { + page: 'wc-admin', + path: '/payments/overview', + source: queryParams.source?.replace( /[^\w-]+/g, '' ) || 'unknown', + from: from, + }; + + // Merge queryParams and baseParams into baseParams, ensuring baseParams takes precedence. + const params = { ...queryParams, ...baseParams }; + + return getAdminUrl( params ); +}; + /** * Returns the URL to the WooPayments settings. * @@ -276,3 +310,15 @@ export const getExportLanguageOptions = () => { }, ]; }; + +/** + * Given an object, remove all properties with null or undefined values. + * + * @param {Object} obj The object to remove empty properties from. + * @return {Object|any} A new object with all properties with null or undefined values removed. + */ +export const objectRemoveEmptyProperties = ( obj ) => { + return Object.keys( obj ) + .filter( ( k ) => obj[ k ] !== null && obj[ k ] !== undefined ) + .reduce( ( a, k ) => ( { ...a, [ k ]: obj[ k ] } ), {} ); +}; diff --git a/client/utils/test/__snapshots__/account-fees.tsx.snap b/client/utils/test/__snapshots__/account-fees.tsx.snap index 2f4ff3a3fde..89321bc7582 100644 --- a/client/utils/test/__snapshots__/account-fees.tsx.snap +++ b/client/utils/test/__snapshots__/account-fees.tsx.snap @@ -44,11 +44,33 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base > Learn more + + (opens in a new tab) + + about WooPayments Fees in your country @@ -101,11 +123,33 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base > Learn more + + (opens in a new tab) + + about WooPayments Fees in your country diff --git a/composer.json b/composer.json index ecad75b952b..284f633e65b 100644 --- a/composer.json +++ b/composer.json @@ -20,13 +20,12 @@ } }, "require": { - "php": ">=7.2", + "php": ">=7.3", "ext-json": "*", - "automattic/jetpack-connection": "2.8.2", - "automattic/jetpack-config": "1.15.2", - "automattic/jetpack-autoloader": "2.11.18", - "automattic/jetpack-identity-crisis": "0.19.0", - "automattic/jetpack-sync": "2.16.3", + "automattic/jetpack-connection": "2.12.4", + "automattic/jetpack-config": "2.0.4", + "automattic/jetpack-autoloader": "3.0.10", + "automattic/jetpack-sync": "3.8.0", "woocommerce/subscriptions-core": "6.7.1" }, "require-dev": { @@ -36,7 +35,7 @@ "woocommerce/action-scheduler": "3.1.6", "kalessil/production-dependencies-guard": "dev-master", "vimeo/psalm": "4.13.1", - "php-stubs/wordpress-stubs": "5.8.2", + "php-stubs/wordpress-stubs": "5.9.6", "php-stubs/woocommerce-stubs": "6.8.0", "rregeer/phpunit-coverage-check": "0.3.1", "yoast/phpunit-polyfills": "1.1.0", diff --git a/composer.lock b/composer.lock index 8642a05ca72..4572fed2932 100644 --- a/composer.lock +++ b/composer.lock @@ -4,28 +4,28 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2fdb6e373349308e7e7855402cf48871", + "content-hash": "ef9fe43c4fffc996cf70034d89fea352", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", - "version": "v2.0.1", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-a8c-mc-stats.git", - "reference": "64748d02bf646e6b000f79dc8e4ea127bbd8df14" + "reference": "5753860f28e1a8629b3c6ab481c1ab75e38a244f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-a8c-mc-stats/zipball/64748d02bf646e6b000f79dc8e4ea127bbd8df14", - "reference": "64748d02bf646e6b000f79dc8e4ea127bbd8df14", + "url": "https://api.github.com/repos/Automattic/jetpack-a8c-mc-stats/zipball/5753860f28e1a8629b3c6ab481c1ab75e38a244f", + "reference": "5753860f28e1a8629b3c6ab481c1ab75e38a244f", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.1.1", - "yoast/phpunit-polyfills": "1.1.0" + "automattic/jetpack-changelogger": "^4.2.6", + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -52,32 +52,32 @@ ], "description": "Used to record internal usage stats for Automattic. Not visible to site owners.", "support": { - "source": "https://github.com/Automattic/jetpack-a8c-mc-stats/tree/v2.0.1" + "source": "https://github.com/Automattic/jetpack-a8c-mc-stats/tree/v2.0.2" }, - "time": "2024-03-12T22:00:11+00:00" + "time": "2024-08-23T14:28:10+00:00" }, { "name": "automattic/jetpack-admin-ui", - "version": "v0.4.2", + "version": "v0.4.4", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-admin-ui.git", - "reference": "cc7062363464f53bb3ae5745754f353c248d6278" + "reference": "18ea3a92f910f7afa9641dadb956b95b75ce6e0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/cc7062363464f53bb3ae5745754f353c248d6278", - "reference": "cc7062363464f53bb3ae5745754f353c248d6278", + "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/18ea3a92f910f7afa9641dadb956b95b75ce6e0f", + "reference": "18ea3a92f910f7afa9641dadb956b95b75ce6e0f", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.2", - "automattic/jetpack-logo": "^2.0.2", + "automattic/jetpack-changelogger": "^4.2.6", + "automattic/jetpack-logo": "^2.0.4", "automattic/wordbless": "dev-master", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -108,33 +108,33 @@ ], "description": "Generic Jetpack wp-admin UI elements", "support": { - "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.4.2" + "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.4.4" }, - "time": "2024-04-22T18:47:32+00:00" + "time": "2024-08-29T08:39:06+00:00" }, { "name": "automattic/jetpack-assets", - "version": "v2.1.10", + "version": "v2.3.5", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-assets.git", - "reference": "f4da7331e5bd2a0c511b8569e1028d5195e4bcf8" + "reference": "e869f7a7780da9b0c1ff9612701fcffde83cda12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/f4da7331e5bd2a0c511b8569e1028d5195e4bcf8", - "reference": "f4da7331e5bd2a0c511b8569e1028d5195e4bcf8", + "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/e869f7a7780da9b0c1ff9612701fcffde83cda12", + "reference": "e869f7a7780da9b0c1ff9612701fcffde83cda12", "shasum": "" }, "require": { - "automattic/jetpack-constants": "^2.0.2", + "automattic/jetpack-constants": "^2.0.4", "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.3", + "automattic/jetpack-changelogger": "^4.2.6", "brain/monkey": "2.6.1", "wikimedia/testing-access-wrapper": "^1.0 || ^2.0 || ^3.0", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -148,7 +148,7 @@ "link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "2.1.x-dev" + "dev-trunk": "2.3.x-dev" } }, "autoload": { @@ -165,30 +165,32 @@ ], "description": "Asset management utilities for Jetpack ecosystem packages", "support": { - "source": "https://github.com/Automattic/jetpack-assets/tree/v2.1.10" + "source": "https://github.com/Automattic/jetpack-assets/tree/v2.3.5" }, - "time": "2024-05-16T10:58:04+00:00" + "time": "2024-08-29T08:39:36+00:00" }, { "name": "automattic/jetpack-autoloader", - "version": "v2.11.18", + "version": "v3.0.10", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-autoloader.git", - "reference": "53cbf0528fa6931c4fa6465bccd37514f9eda720" + "reference": "ec4c465ce6a47fb15c15ab0224ec5b1272422d3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/53cbf0528fa6931c4fa6465bccd37514f9eda720", - "reference": "53cbf0528fa6931c4fa6465bccd37514f9eda720", + "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/ec4c465ce6a47fb15c15ab0224ec5b1272422d3e", + "reference": "ec4c465ce6a47fb15c15ab0224ec5b1272422d3e", "shasum": "" }, "require": { - "composer-plugin-api": "^1.1 || ^2.0" + "composer-plugin-api": "^1.1 || ^2.0", + "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.2", - "yoast/phpunit-polyfills": "1.0.4" + "automattic/jetpack-changelogger": "^4.2.6", + "composer/composer": "^1.1 || ^2.0", + "yoast/phpunit-polyfills": "^1.1.1" }, "type": "composer-plugin", "extra": { @@ -198,8 +200,11 @@ "changelogger": { "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" }, + "version-constants": { + "::VERSION": "src/AutoloadGenerator.php" + }, "branch-alias": { - "dev-trunk": "2.11.x-dev" + "dev-trunk": "3.0.x-dev" } }, "autoload": { @@ -215,27 +220,51 @@ "GPL-2.0-or-later" ], "description": "Creates a custom autoloader for a plugin or theme.", + "keywords": [ + "autoload", + "autoloader", + "composer", + "jetpack", + "plugin", + "wordpress" + ], "support": { - "source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.11.18" + "source": "https://github.com/Automattic/jetpack-autoloader/tree/v3.0.10" }, - "time": "2023-03-29T12:51:59+00:00" + "time": "2024-08-26T14:49:14+00:00" }, { "name": "automattic/jetpack-config", - "version": "v1.15.2", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-config.git", - "reference": "f1fa6e24a89192336a1499968bf8c68e173b6e34" + "reference": "9f075c81bae6fd638e0b3183612cda5cc9e01e06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-config/zipball/f1fa6e24a89192336a1499968bf8c68e173b6e34", - "reference": "f1fa6e24a89192336a1499968bf8c68e173b6e34", + "url": "https://api.github.com/repos/Automattic/jetpack-config/zipball/9f075c81bae6fd638e0b3183612cda5cc9e01e06", + "reference": "9f075c81bae6fd638e0b3183612cda5cc9e01e06", "shasum": "" }, + "require": { + "php": ">=7.0" + }, "require-dev": { - "automattic/jetpack-changelogger": "^3.3.2" + "automattic/jetpack-changelogger": "^4.2.4", + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-import": "@dev", + "automattic/jetpack-jitm": "@dev", + "automattic/jetpack-post-list": "@dev", + "automattic/jetpack-publicize": "@dev", + "automattic/jetpack-search": "@dev", + "automattic/jetpack-stats": "@dev", + "automattic/jetpack-stats-admin": "@dev", + "automattic/jetpack-sync": "@dev", + "automattic/jetpack-videopress": "@dev", + "automattic/jetpack-waf": "@dev", + "automattic/jetpack-wordads": "@dev", + "automattic/jetpack-yoast-promo": "@dev" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -249,7 +278,24 @@ "link-template": "https://github.com/Automattic/jetpack-config/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "1.15.x-dev" + "dev-trunk": "2.0.x-dev" + }, + "dependencies": { + "test-only": [ + "packages/connection", + "packages/import", + "packages/jitm", + "packages/post-list", + "packages/publicize", + "packages/search", + "packages/stats", + "packages/stats-admin", + "packages/sync", + "packages/videopress", + "packages/waf", + "packages/wordads", + "packages/yoast-promo" + ] } }, "autoload": { @@ -263,41 +309,39 @@ ], "description": "Jetpack configuration package that initializes other packages and configures Jetpack's functionality. Can be used as a base for all variants of Jetpack package usage.", "support": { - "source": "https://github.com/Automattic/jetpack-config/tree/v1.15.2" + "source": "https://github.com/Automattic/jetpack-config/tree/v2.0.4" }, - "time": "2023-04-10T11:43:31+00:00" + "time": "2024-06-24T19:22:07+00:00" }, { "name": "automattic/jetpack-connection", - "version": "v2.8.2", + "version": "v2.12.4", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-connection.git", - "reference": "11441e20c4fa657f182bc94861d14ed5e320ab01" + "reference": "35dd5b89b9936555ac185e83a489f41655974e70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/11441e20c4fa657f182bc94861d14ed5e320ab01", - "reference": "11441e20c4fa657f182bc94861d14ed5e320ab01", + "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/35dd5b89b9936555ac185e83a489f41655974e70", + "reference": "35dd5b89b9936555ac185e83a489f41655974e70", "shasum": "" }, "require": { - "automattic/jetpack-a8c-mc-stats": "^2.0.1", - "automattic/jetpack-admin-ui": "^0.4.2", - "automattic/jetpack-assets": "^2.1.10", - "automattic/jetpack-constants": "^2.0.2", - "automattic/jetpack-redirect": "^2.0.2", - "automattic/jetpack-roles": "^2.0.2", - "automattic/jetpack-status": "^3.0.3", + "automattic/jetpack-a8c-mc-stats": "^2.0.2", + "automattic/jetpack-admin-ui": "^0.4.3", + "automattic/jetpack-assets": "^2.3.4", + "automattic/jetpack-constants": "^2.0.4", + "automattic/jetpack-redirect": "^2.0.3", + "automattic/jetpack-roles": "^2.0.3", + "automattic/jetpack-status": "^3.3.4", "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.3", - "automattic/jetpack-licensing": "@dev", - "automattic/jetpack-sync": "@dev", + "automattic/jetpack-changelogger": "^4.2.6", "automattic/wordbless": "@dev", "brain/monkey": "2.6.1", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -314,7 +358,7 @@ "link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "2.8.x-dev" + "dev-trunk": "2.12.x-dev" }, "dependencies": { "test-only": [ @@ -327,7 +371,8 @@ "classmap": [ "legacy", "src/", - "src/webhooks" + "src/webhooks", + "src/identity-crisis" ] }, "notification-url": "https://packagist.org/downloads/", @@ -336,31 +381,31 @@ ], "description": "Everything needed to connect to the Jetpack infrastructure", "support": { - "source": "https://github.com/Automattic/jetpack-connection/tree/v2.8.2" + "source": "https://github.com/Automattic/jetpack-connection/tree/v2.12.4" }, - "time": "2024-05-16T10:58:12+00:00" + "time": "2024-08-23T14:29:32+00:00" }, { "name": "automattic/jetpack-constants", - "version": "v2.0.2", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-constants.git", - "reference": "6f7991f9e4475d4e2c3fd843111abfadb8a8c187" + "reference": "f6958c313a34c5e92171c45a57d9dc978e5975ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/6f7991f9e4475d4e2c3fd843111abfadb8a8c187", - "reference": "6f7991f9e4475d4e2c3fd843111abfadb8a8c187", + "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/f6958c313a34c5e92171c45a57d9dc978e5975ed", + "reference": "f6958c313a34c5e92171c45a57d9dc978e5975ed", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.2", + "automattic/jetpack-changelogger": "^4.2.6", "brain/monkey": "2.6.1", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -387,91 +432,31 @@ ], "description": "A wrapper for defining constants in a more testable way.", "support": { - "source": "https://github.com/Automattic/jetpack-constants/tree/v2.0.2" - }, - "time": "2024-04-30T19:01:57+00:00" - }, - { - "name": "automattic/jetpack-identity-crisis", - "version": "v0.19.0", - "source": { - "type": "git", - "url": "https://github.com/Automattic/jetpack-identity-crisis.git", - "reference": "a29d1be468e0b567c6e4248c6a2017e9597d5ca8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-identity-crisis/zipball/a29d1be468e0b567c6e4248c6a2017e9597d5ca8", - "reference": "a29d1be468e0b567c6e4248c6a2017e9597d5ca8", - "shasum": "" - }, - "require": { - "automattic/jetpack-assets": "^2.1.10", - "automattic/jetpack-connection": "^2.8.2", - "automattic/jetpack-constants": "^2.0.2", - "automattic/jetpack-logo": "^2.0.2", - "automattic/jetpack-status": "^3.0.3", - "php": ">=7.0" - }, - "require-dev": { - "automattic/jetpack-changelogger": "^4.2.3", - "automattic/wordbless": "@dev", - "yoast/phpunit-polyfills": "1.1.0" - }, - "suggest": { - "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." - }, - "type": "jetpack-library", - "extra": { - "autotagger": true, - "mirror-repo": "Automattic/jetpack-identity-crisis", - "textdomain": "jetpack-idc", - "version-constants": { - "::PACKAGE_VERSION": "src/class-identity-crisis.php" - }, - "changelogger": { - "link-template": "https://github.com/Automattic/jetpack-identity-crisis/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "0.19.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "description": "Identity Crisis.", - "support": { - "source": "https://github.com/Automattic/jetpack-identity-crisis/tree/v0.19.0" + "source": "https://github.com/Automattic/jetpack-constants/tree/v2.0.4" }, - "time": "2024-05-16T10:58:31+00:00" + "time": "2024-08-23T14:28:14+00:00" }, { "name": "automattic/jetpack-ip", - "version": "v0.2.2", + "version": "v0.2.3", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-ip.git", - "reference": "b3efffb57901e236c3c1e7299b575563e1937013" + "reference": "f7a42b1603a24775c6f20eef2ac5cba3d6b37194" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-ip/zipball/b3efffb57901e236c3c1e7299b575563e1937013", - "reference": "b3efffb57901e236c3c1e7299b575563e1937013", + "url": "https://api.github.com/repos/Automattic/jetpack-ip/zipball/f7a42b1603a24775c6f20eef2ac5cba3d6b37194", + "reference": "f7a42b1603a24775c6f20eef2ac5cba3d6b37194", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.1.1", + "automattic/jetpack-changelogger": "^4.2.6", "brain/monkey": "2.6.1", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -502,81 +487,31 @@ ], "description": "Utilities for working with IP addresses.", "support": { - "source": "https://github.com/Automattic/jetpack-ip/tree/v0.2.2" - }, - "time": "2024-03-12T22:00:05+00:00" - }, - { - "name": "automattic/jetpack-logo", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/Automattic/jetpack-logo.git", - "reference": "25a1824973439f6cbb1e4d9bb88cb7fdd9c83305" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-logo/zipball/25a1824973439f6cbb1e4d9bb88cb7fdd9c83305", - "reference": "25a1824973439f6cbb1e4d9bb88cb7fdd9c83305", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "automattic/jetpack-changelogger": "^4.1.2", - "yoast/phpunit-polyfills": "1.1.0" - }, - "suggest": { - "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." - }, - "type": "jetpack-library", - "extra": { - "autotagger": true, - "mirror-repo": "Automattic/jetpack-logo", - "changelogger": { - "link-template": "https://github.com/Automattic/jetpack-logo/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "description": "A logo for Jetpack", - "support": { - "source": "https://github.com/Automattic/jetpack-logo/tree/v2.0.2" + "source": "https://github.com/Automattic/jetpack-ip/tree/v0.2.3" }, - "time": "2024-03-18T17:10:26+00:00" + "time": "2024-08-23T14:28:05+00:00" }, { "name": "automattic/jetpack-password-checker", - "version": "v0.3.1", + "version": "v0.3.2", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-password-checker.git", - "reference": "d9be23791e6f38debeacbf74174903f223ddfd89" + "reference": "bdf70591123932112e447e295d7f174b5c0e3a44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-password-checker/zipball/d9be23791e6f38debeacbf74174903f223ddfd89", - "reference": "d9be23791e6f38debeacbf74174903f223ddfd89", + "url": "https://api.github.com/repos/Automattic/jetpack-password-checker/zipball/bdf70591123932112e447e295d7f174b5c0e3a44", + "reference": "bdf70591123932112e447e295d7f174b5c0e3a44", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.1.1", + "automattic/jetpack-changelogger": "^4.2.6", "automattic/wordbless": "@dev", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -604,32 +539,32 @@ ], "description": "Password Checker.", "support": { - "source": "https://github.com/Automattic/jetpack-password-checker/tree/v0.3.1" + "source": "https://github.com/Automattic/jetpack-password-checker/tree/v0.3.2" }, - "time": "2024-03-14T20:42:38+00:00" + "time": "2024-08-23T14:28:17+00:00" }, { "name": "automattic/jetpack-redirect", - "version": "v2.0.2", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-redirect.git", - "reference": "7214fcd3684eb99178d6368c01f8778a182444cb" + "reference": "2c049bb08f736dc0dbafac7eaebea6f97cf8019e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-redirect/zipball/7214fcd3684eb99178d6368c01f8778a182444cb", - "reference": "7214fcd3684eb99178d6368c01f8778a182444cb", + "url": "https://api.github.com/repos/Automattic/jetpack-redirect/zipball/2c049bb08f736dc0dbafac7eaebea6f97cf8019e", + "reference": "2c049bb08f736dc0dbafac7eaebea6f97cf8019e", "shasum": "" }, "require": { - "automattic/jetpack-status": "^3.0.0", + "automattic/jetpack-status": "^3.3.4", "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.2", + "automattic/jetpack-changelogger": "^4.2.6", "brain/monkey": "2.6.1", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -656,31 +591,31 @@ ], "description": "Utilities to build URLs to the jetpack.com/redirect/ service", "support": { - "source": "https://github.com/Automattic/jetpack-redirect/tree/v2.0.2" + "source": "https://github.com/Automattic/jetpack-redirect/tree/v2.0.3" }, - "time": "2024-04-25T07:24:30+00:00" + "time": "2024-08-23T14:28:46+00:00" }, { "name": "automattic/jetpack-roles", - "version": "v2.0.2", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-roles.git", - "reference": "7d452c94509612e94e045b66bbfabee43fdf8728" + "reference": "32e45299a6ff93de0b1f4c71e6669f15917220fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/7d452c94509612e94e045b66bbfabee43fdf8728", - "reference": "7d452c94509612e94e045b66bbfabee43fdf8728", + "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/32e45299a6ff93de0b1f4c71e6669f15917220fb", + "reference": "32e45299a6ff93de0b1f4c71e6669f15917220fb", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.2", + "automattic/jetpack-changelogger": "^4.2.6", "brain/monkey": "2.6.1", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -707,36 +642,35 @@ ], "description": "Utilities, related with user roles and capabilities.", "support": { - "source": "https://github.com/Automattic/jetpack-roles/tree/v2.0.2" + "source": "https://github.com/Automattic/jetpack-roles/tree/v2.0.3" }, - "time": "2024-04-22T18:47:11+00:00" + "time": "2024-08-23T14:28:15+00:00" }, { "name": "automattic/jetpack-status", - "version": "v3.0.3", + "version": "v3.3.4", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-status.git", - "reference": "dee3a6f2dcc129bd3869c44ba661484adda0a2f8" + "reference": "cf023b164ded674d66998b5b5870a3b6cf26679a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/dee3a6f2dcc129bd3869c44ba661484adda0a2f8", - "reference": "dee3a6f2dcc129bd3869c44ba661484adda0a2f8", + "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/cf023b164ded674d66998b5b5870a3b6cf26679a", + "reference": "cf023b164ded674d66998b5b5870a3b6cf26679a", "shasum": "" }, "require": { - "automattic/jetpack-constants": "^2.0.2", + "automattic/jetpack-constants": "^2.0.4", "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.3", + "automattic/jetpack-changelogger": "^4.2.6", "automattic/jetpack-connection": "@dev", - "automattic/jetpack-identity-crisis": "@dev", - "automattic/jetpack-ip": "^0.2.2", + "automattic/jetpack-ip": "^0.2.3", "automattic/jetpack-plans": "@dev", "brain/monkey": "2.6.1", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -749,12 +683,11 @@ "link-template": "https://github.com/Automattic/jetpack-status/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "3.0.x-dev" + "dev-trunk": "3.3.x-dev" }, "dependencies": { "test-only": [ "packages/connection", - "packages/identity-crisis", "packages/plans" ] } @@ -770,40 +703,39 @@ ], "description": "Used to retrieve information about the current status of Jetpack and the site overall.", "support": { - "source": "https://github.com/Automattic/jetpack-status/tree/v3.0.3" + "source": "https://github.com/Automattic/jetpack-status/tree/v3.3.4" }, - "time": "2024-05-08T10:06:11+00:00" + "time": "2024-08-23T14:28:43+00:00" }, { "name": "automattic/jetpack-sync", - "version": "v2.16.3", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-sync.git", - "reference": "f98ab01117b57ac948616a284917f6ff3db8cbcf" + "reference": "30b29f0c5a27e01cbf2fa592fbde97f617665153" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-sync/zipball/f98ab01117b57ac948616a284917f6ff3db8cbcf", - "reference": "f98ab01117b57ac948616a284917f6ff3db8cbcf", + "url": "https://api.github.com/repos/Automattic/jetpack-sync/zipball/30b29f0c5a27e01cbf2fa592fbde97f617665153", + "reference": "30b29f0c5a27e01cbf2fa592fbde97f617665153", "shasum": "" }, "require": { - "automattic/jetpack-connection": "^2.8.2", - "automattic/jetpack-constants": "^2.0.2", - "automattic/jetpack-identity-crisis": "^0.19.0", - "automattic/jetpack-ip": "^0.2.2", - "automattic/jetpack-password-checker": "^0.3.1", - "automattic/jetpack-roles": "^2.0.2", - "automattic/jetpack-status": "^3.0.3", + "automattic/jetpack-connection": "^2.12.4", + "automattic/jetpack-constants": "^2.0.4", + "automattic/jetpack-ip": "^0.2.3", + "automattic/jetpack-password-checker": "^0.3.2", + "automattic/jetpack-roles": "^2.0.3", + "automattic/jetpack-status": "^3.3.4", "php": ">=7.0" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.3", + "automattic/jetpack-changelogger": "^4.2.6", "automattic/jetpack-search": "@dev", - "automattic/jetpack-waf": "^0.16.7", + "automattic/jetpack-waf": "^0.18.4", "automattic/wordbless": "@dev", - "yoast/phpunit-polyfills": "1.1.0" + "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." @@ -820,7 +752,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "2.16.x-dev" + "dev-trunk": "3.8.x-dev" }, "dependencies": { "test-only": [ @@ -840,9 +772,9 @@ ], "description": "Everything needed to allow syncing to the WP.com infrastructure.", "support": { - "source": "https://github.com/Automattic/jetpack-sync/tree/v2.16.3" + "source": "https://github.com/Automattic/jetpack-sync/tree/v3.8.0" }, - "time": "2024-05-16T10:58:32+00:00" + "time": "2024-08-26T14:49:56+00:00" }, { "name": "composer/installers", @@ -1053,16 +985,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.2", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { @@ -1074,8 +1006,8 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", "extra": { @@ -1130,7 +1062,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "source": "https://github.com/amphp/amp/tree/v2.6.4" }, "funding": [ { @@ -1138,20 +1070,20 @@ "type": "github" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2024-03-21T18:52:26+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { @@ -1167,11 +1099,6 @@ "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php" @@ -1195,7 +1122,7 @@ } ], "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "homepage": "https://amphp.org/byte-stream", "keywords": [ "amp", "amphp", @@ -1205,9 +1132,8 @@ "stream" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { @@ -1215,7 +1141,7 @@ "type": "github" } ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { "name": "automattic/jetpack-changelogger", @@ -1632,16 +1558,16 @@ }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -1693,7 +1619,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -1709,7 +1635,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-07-12T11:35:52+00:00" }, { "name": "composer/xdebug-handler", @@ -2384,16 +2310,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -2401,11 +2327,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -2431,7 +2358,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -2439,7 +2366,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "netresearch/jsonmapper", @@ -2494,21 +2421,21 @@ }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.19.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", @@ -2544,9 +2471,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-03-17T08:10:35+00:00" }, { "name": "openlss/lib-array2xml", @@ -2765,24 +2692,25 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v5.8.2", + "version": "v5.9.6", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "67fd773742b7be5b4463f40318b0b4890a07033b" + "reference": "6a18d938d0aef39d091505a4a35b025fb6c10098" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/67fd773742b7be5b4463f40318b0b4890a07033b", - "reference": "67fd773742b7be5b4463f40318b0b4890a07033b", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6a18d938d0aef39d091505a4a35b025fb6c10098", + "reference": "6a18d938d0aef39d091505a4a35b025fb6c10098", "shasum": "" }, - "replace": { - "giacocorsiglia/wordpress-stubs": "*" - }, "require-dev": { - "giacocorsiglia/stubs-generator": "^0.5.0", - "php": "~7.1" + "nikic/php-parser": "< 4.12.0", + "php": "~7.3 || ~8.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpstan": "^1.10.12", + "phpunit/phpunit": "^9.5" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", @@ -2803,9 +2731,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v5.8.2" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v5.9.6" }, - "time": "2021-11-11T13:57:00+00:00" + "time": "2023-05-18T04:34:27+00:00" }, { "name": "phpcompatibility/php-compatibility", @@ -2871,28 +2799,28 @@ }, { "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "paragonie/random_compat": "dev-master", "paragonie/sodium_compat": "dev-master" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -2922,22 +2850,37 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" }, - "time": "2022-10-25T01:46:02+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:30:46+00:00" }, { "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.4", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/01c1ff2704a58e46f0cb1ca9d06aee07b3589082", + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082", "shasum": "" }, "require": { @@ -2945,10 +2888,10 @@ "phpcompatibility/phpcompatibility-paragonie": "^1.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7" + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -2977,9 +2920,24 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" }, - "time": "2022-10-24T09:00:36+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:37:59+00:00" }, { "name": "phpcsstandards/phpcsextra", @@ -3061,22 +3019,22 @@ }, { "name": "phpcsstandards/phpcsutils", - "version": "1.0.9", + "version": "1.0.12", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "908247bc65010c7b7541a9551e002db12e9dae70" + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/908247bc65010c7b7541a9551e002db12e9dae70", - "reference": "908247bc65010c7b7541a9551e002db12e9dae70", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.8.0 || 4.0.x-dev@dev" + "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" }, "require-dev": { "ext-filter": "*", @@ -3145,7 +3103,7 @@ "type": "open_collective" } ], - "time": "2023-12-08T14:50:00+00:00" + "time": "2024-05-20T13:34:27+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3386,16 +3344,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.26.0", + "version": "1.30.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", "shasum": "" }, "require": { @@ -3427,41 +3385,41 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" }, - "time": "2024-02-23T16:05:55+00:00" + "time": "2024-08-29T09:54:52+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -3470,7 +3428,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -3499,7 +3457,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -3507,7 +3465,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4025,16 +3983,16 @@ }, { "name": "react/stream", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66" + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/6fbc9672905c7d5a885f2da2fc696f65840f4a66", - "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { @@ -4044,7 +4002,7 @@ }, "require-dev": { "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { @@ -4091,7 +4049,7 @@ ], "support": { "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.3.0" + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, "funding": [ { @@ -4099,7 +4057,7 @@ "type": "open_collective" } ], - "time": "2023-06-16T10:52:11+00:00" + "time": "2024-06-11T12:45:25+00:00" }, { "name": "rregeer/phpunit-coverage-check", @@ -5112,16 +5070,16 @@ }, { "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.11.17", + "version": "v2.11.19", "source": { "type": "git", "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049" + "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/3b71162a6bf0cde2bff1752e40a1788d8273d049", - "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", + "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", "shasum": "" }, "require": { @@ -5166,7 +5124,7 @@ "source": "https://github.com/sirbrillig/phpcs-variable-analysis", "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" }, - "time": "2023-08-05T23:46:11+00:00" + "time": "2024-06-26T20:08:34+00:00" }, { "name": "slevomat/coding-standard", @@ -5297,16 +5255,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", "shasum": "" }, "require": { @@ -5373,20 +5331,20 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" + "time": "2024-07-21T23:26:44+00:00" }, { "name": "symfony/console", - "version": "v5.4.36", + "version": "v5.4.42", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e" + "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", - "reference": "39f75d9d73d0c11952fdcecf4877b4d0f62a8f6e", + "url": "https://api.github.com/repos/symfony/console/zipball/cef62396a0477e94fc52e87a17c6e5c32e226b7f", + "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f", "shasum": "" }, "require": { @@ -5456,7 +5414,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.36" + "source": "https://github.com/symfony/console/tree/v5.4.42" }, "funding": [ { @@ -5472,20 +5430,20 @@ "type": "tidelift" } ], - "time": "2024-02-20T16:33:57+00:00" + "time": "2024-07-26T12:21:55+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "80d075412b557d41002320b96a096ca65aa2c98d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", + "reference": "80d075412b557d41002320b96a096ca65aa2c98d", "shasum": "" }, "require": { @@ -5523,7 +5481,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3" }, "funding": [ { @@ -5539,20 +5497,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-01-24T14:02:46+00:00" }, { "name": "symfony/finder", - "version": "v5.4.35", + "version": "v5.4.42", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435" + "reference": "0724c51fa067b198e36506d2864e09a52180998a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/abe6d6f77d9465fed3cd2d029b29d03b56b56435", - "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435", + "url": "https://api.github.com/repos/symfony/finder/zipball/0724c51fa067b198e36506d2864e09a52180998a", + "reference": "0724c51fa067b198e36506d2864e09a52180998a", "shasum": "" }, "require": { @@ -5586,7 +5544,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.35" + "source": "https://github.com/symfony/finder/tree/v5.4.42" }, "funding": [ { @@ -5602,20 +5560,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-07-22T08:53:29+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { @@ -5665,7 +5623,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -5681,20 +5639,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { @@ -5743,7 +5701,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" }, "funding": [ { @@ -5759,20 +5717,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { @@ -5824,7 +5782,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" }, "funding": [ { @@ -5840,20 +5798,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -5904,7 +5862,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -5920,20 +5878,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", "shasum": "" }, "require": { @@ -5980,7 +5938,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" }, "funding": [ { @@ -5996,20 +5954,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -6060,7 +6018,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -6076,20 +6034,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/process", - "version": "v5.4.36", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975" + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4fdf34004f149cc20b2f51d7d119aa500caad975", - "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975", + "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046", "shasum": "" }, "require": { @@ -6122,7 +6080,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.36" + "source": "https://github.com/symfony/process/tree/v5.4.40" }, "funding": [ { @@ -6138,20 +6096,20 @@ "type": "tidelift" } ], - "time": "2024-02-12T15:49:53+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", "shasum": "" }, "require": { @@ -6205,7 +6163,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" }, "funding": [ { @@ -6221,20 +6179,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-04-21T15:04:16+00:00" }, { "name": "symfony/string", - "version": "v5.4.36", + "version": "v5.4.42", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b" + "reference": "909cec913edea162a3b2836788228ad45fcab337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/4e232c83622bd8cd32b794216aa29d0d266d353b", - "reference": "4e232c83622bd8cd32b794216aa29d0d266d353b", + "url": "https://api.github.com/repos/symfony/string/zipball/909cec913edea162a3b2836788228ad45fcab337", + "reference": "909cec913edea162a3b2836788228ad45fcab337", "shasum": "" }, "require": { @@ -6291,7 +6249,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.36" + "source": "https://github.com/symfony/string/tree/v5.4.42" }, "funding": [ { @@ -6307,20 +6265,20 @@ "type": "tidelift" } ], - "time": "2024-02-01T08:49:30+00:00" + "time": "2024-07-20T18:38:32+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.35", + "version": "v5.4.40", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4" + "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e78db7f5c70a21f0417a31f414c4a95fe76c07e4", - "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4", + "url": "https://api.github.com/repos/symfony/yaml/zipball/81cad0ceab3d61fe14fe941ff18a230ac9c80f83", + "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83", "shasum": "" }, "require": { @@ -6366,7 +6324,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.35" + "source": "https://github.com/symfony/yaml/tree/v5.4.40" }, "funding": [ { @@ -6382,7 +6340,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T13:51:25+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { "name": "theseer/tokenizer", @@ -6817,16 +6775,16 @@ }, { "name": "wp-coding-standards/wpcs", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", - "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", "shasum": "" }, "require": { @@ -6835,16 +6793,16 @@ "ext-tokenizer": "*", "ext-xmlreader": "*", "php": ">=5.4", - "phpcsstandards/phpcsextra": "^1.1.0", - "phpcsstandards/phpcsutils": "^1.0.8", - "squizlabs/php_codesniffer": "^3.7.2" + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.0", "phpcsstandards/phpcsdevtools": "^1.2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "suggest": { "ext-iconv": "For improved results", @@ -6875,11 +6833,11 @@ }, "funding": [ { - "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", + "url": "https://opencollective.com/php_codesniffer", "type": "custom" } ], - "time": "2023-09-14T07:06:09+00:00" + "time": "2024-03-25T16:39:00+00:00" }, { "name": "yoast/phpunit-polyfills", @@ -7007,7 +6965,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.2", + "php": ">=7.3", "ext-json": "*" }, "platform-dev": [], diff --git a/docker-compose.yml b/docker-compose.yml index 2804781d6bc..8c35b58b439 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: dockerfile: ./docker/wordpress_xdebug/Dockerfile args: - XDEBUG_REMOTE_PORT=9000 # IDE/Editor's listener port + - XDEBUG_START_WITH_REQUEST=trigger container_name: woocommerce_payments_wordpress image: woocommerce_payments_wordpress restart: always diff --git a/docker/wordpress_xdebug/Dockerfile b/docker/wordpress_xdebug/Dockerfile index 7b612f20806..616655012f3 100755 --- a/docker/wordpress_xdebug/Dockerfile +++ b/docker/wordpress_xdebug/Dockerfile @@ -1,15 +1,15 @@ -FROM wordpress:php7.4 +FROM wordpress:php8.1 ARG XDEBUG_REMOTE_PORT ARG XDEBUG_REMOTE_HOST=host.docker.internal -RUN pecl install xdebug-2.9.8 \ - && echo 'xdebug.remote_enable=1' >> $PHP_INI_DIR/php.ini \ - && echo "xdebug.remote_port=$XDEBUG_REMOTE_PORT" >> $PHP_INI_DIR/php.ini \ - && echo "xdebug.remote_host=$XDEBUG_REMOTE_HOST" >> $PHP_INI_DIR/php.ini \ - && echo 'xdebug.remote_autostart=0' >> $PHP_INI_DIR/php.ini \ - && docker-php-ext-enable xdebug +ARG XDEBUG_START_WITH_REQUEST=trigger +RUN pecl install xdebug \ + && echo 'xdebug.mode=coverage,debug' >> $PHP_INI_DIR/php.ini \ + && echo "xdebug.client_port=${XDEBUG_REMOTE_PORT}" >> $PHP_INI_DIR/php.ini \ + && echo "xdebug.client_host=${XDEBUG_REMOTE_HOST}" >> $PHP_INI_DIR/php.ini \ + && echo "xdebug.start_with_request=${XDEBUG_START_WITH_REQUEST}" >> $PHP_INI_DIR/php.ini \ + && docker-php-ext-enable xdebug RUN apt-get update \ - && apt-get install --assume-yes --quiet --no-install-recommends gnupg2 subversion mariadb-client less jq -RUN apt-get install -y openssh-client + && apt-get install --assume-yes --quiet --no-install-recommends gnupg2 subversion mariadb-client less jq openssh-client RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ && chmod +x wp-cli.phar \ && mv wp-cli.phar /usr/local/bin/wp diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 448d85831b2..de577e4bcad 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -202,7 +202,8 @@ public function init_hooks() { // Add menu items. add_action( 'admin_menu', [ $this, 'add_payments_menu' ], 0 ); - add_action( 'admin_init', [ $this, 'maybe_redirect_from_payments_admin_child_pages' ], 11 ); // Run this after the WC setup wizard and onboarding redirection logic. + // Run this after the redirects in WC_Payments_Account. + add_action( 'admin_init', [ $this, 'maybe_redirect_from_payments_admin_child_pages' ], 16 ); add_action( 'admin_enqueue_scripts', [ $this, 'register_payments_scripts' ], 9 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_payments_scripts' ], 9 ); add_action( 'woocommerce_admin_field_payment_gateways', [ $this, 'payment_gateways_container' ] ); @@ -347,6 +348,23 @@ public function add_payments_menu() { remove_submenu_page( 'wc-admin&path=/payments/connect', 'wc-admin&path=/payments/onboarding' ); } + // Register /payments/onboarding/kyc only when we have a Stripe account, but the Stripe KYC is not finished (details not submitted). + if ( WC_Payments_Features::is_embedded_kyc_enabled() && $this->account->is_stripe_connected() && ! $this->account->is_details_submitted() ) { + wc_admin_register_page( + [ + 'id' => 'wc-payments-onboarding-kyc', + 'title' => __( 'Continue onboarding', 'woocommerce-payments' ), + 'parent' => 'wc-payments', + 'path' => '/payments/onboarding/kyc', + 'capability' => 'manage_woocommerce', + 'nav_args' => [ + 'parent' => 'wc-payments', + ], + ] + ); + remove_submenu_page( 'wc-admin&path=/payments/connect', 'wc-admin&path=/payments/onboarding/kyc' ); + } + if ( $should_render_full_menu ) { if ( $this->account->is_card_present_eligible() && $this->account->has_card_readers_available() ) { $this->admin_child_pages['wc-payments-card-readers'] = [ @@ -598,6 +616,15 @@ public function enqueue_payments_scripts() { wp_enqueue_style( 'WCPAY_ADMIN_SETTINGS' ); } + // Enqueue the onboarding scripts if the user is on the onboarding page. + if ( WC_Payments_Utils::is_onboarding_page() ) { + wp_localize_script( + 'WCPAY_ONBOARDING_SETTINGS', + 'wcpayOnboardingSettings', + [] + ); + } + // TODO: Try to enqueue the JS and CSS bundles lazily (will require changes on WC-Admin). $current_screen = get_current_screen() ? get_current_screen()->base : null; if ( wc_admin_is_registered_page() || 'widgets' === $current_screen ) { @@ -849,6 +876,7 @@ private function get_js_settings(): array { // Set this flag for use in the front-end to alter messages and notices if on-boarding has been disabled. 'onBoardingDisabled' => WC_Payments_Account::is_on_boarding_disabled(), 'onboardingFieldsData' => $this->onboarding_service->get_fields_data( get_user_locale() ), + 'onboardingEmbeddedKycInProgress' => $this->onboarding_service->is_embedded_kyc_in_progress(), 'errorMessage' => $error_message, 'featureFlags' => $this->get_frontend_feature_flags(), 'isSubscriptionsActive' => class_exists( 'WC_Subscriptions' ) && version_compare( WC_Subscriptions::$version, '2.2.0', '>=' ), @@ -1093,44 +1121,56 @@ private function get_settings_menu_item_name() { } /** - * If the user is attempting to view a WCPay admin page without a connected Stripe account, - * redirect them to the connect account page. + * Redirects WCPay admin pages to the Connect page for stores that + * don't have a working Jetpack connection or a valid connected Stripe account. + * + * Please note that the overview page is handled separately in the + * `WC_Payments_Account::maybe_redirect_from_overview_page` method, before this method is called (priority 15 vs 16). + * + * IMPORTANT: The logic should be kept in sync with the one in maybe_redirect_from_connect_page to avoid loops. + * + * @see WC_Payments_Account::maybe_redirect_from_overview_page() for overview page handling. + * @see WC_Payments_Account::maybe_handle_onboarding() for connect links handling. + * + * @return bool True if a redirection happened, false otherwise. */ - public function maybe_redirect_from_payments_admin_child_pages() { - if ( ! current_user_can( 'manage_woocommerce' ) ) { - return; - } - if ( wp_doing_ajax() ) { - return; + public function maybe_redirect_from_payments_admin_child_pages(): bool { + if ( wp_doing_ajax() || ! current_user_can( 'manage_woocommerce' ) ) { + return false; } $url_params = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification - if ( empty( $url_params['page'] ) || 'wc-admin' !== $url_params['page'] ) { - return; + return false; } $current_path = ! empty( $url_params['path'] ) ? $url_params['path'] : ''; - if ( empty( $current_path ) ) { - return; + return false; } + // If the current path doesn't match any of the paths we're interested in, do not redirect. $page_paths = []; - foreach ( $this->admin_child_pages as $payments_child_page ) { $page_paths[] = preg_quote( $payments_child_page['path'], '/' ); } - if ( ! preg_match( '/^(' . implode( '|', $page_paths ) . ')/', $current_path ) ) { - return; + return false; } - if ( $this->account->is_stripe_connected( true, true ) ) { - return; + // If everything is NOT in good working condition, redirect to Payments Connect page. + if ( ! $this->account->has_working_jetpack_connection() || ! $this->account->is_stripe_account_valid() ) { + $this->account->redirect_to_onboarding_welcome_page( + sprintf( + /* translators: 1: WooPayments. */ + __( 'Please complete your %1$s setup to continue using it.', 'woocommerce-payments' ), + 'WooPayments' + ) + ); + return true; } - $this->account->redirect_to_onboarding_welcome_page(); + return false; } /** diff --git a/includes/admin/class-wc-rest-payments-onboarding-controller.php b/includes/admin/class-wc-rest-payments-onboarding-controller.php index bfad476ce87..1a9553d46c8 100644 --- a/includes/admin/class-wc-rest-payments-onboarding-controller.php +++ b/includes/admin/class-wc-rest-payments-onboarding-controller.php @@ -5,8 +5,7 @@ * @package WooCommerce\Payments\Admin */ -use WCPay\Exceptions\API_Exception; -use WCPay\Exceptions\Rest_Request_Exception; +use WCPay\Logger; defined( 'ABSPATH' ) || exit; @@ -49,6 +48,87 @@ public function __construct( * Configure REST API routes. */ public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/kyc/session', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_embedded_kyc_session' ], + 'permission_callback' => [ $this, 'check_permission' ], + 'args' => [ + 'progressive' => [ + 'required' => false, + 'description' => 'Whether the session is for progressive onboarding.', + 'type' => 'string', + ], + 'collect_payout_requirements' => [ + 'required' => false, + 'description' => 'Whether the session is for collecting payout requirements.', + 'type' => 'string', + ], + 'self_assessment' => [ + 'required' => false, + 'description' => 'The self-assessment data.', + 'type' => 'object', + 'properties' => [ + 'country' => [ + 'type' => 'string', + 'description' => 'The country code where the company is legally registered.', + 'required' => true, + ], + 'business_type' => [ + 'type' => 'string', + 'description' => 'The company incorporation type.', + 'required' => true, + ], + 'mcc' => [ + 'type' => 'string', + 'description' => 'The merchant category code. This can either be a true MCC or an MCCs tree item id from the onboarding form.', + 'required' => true, + ], + 'annual_revenue' => [ + 'type' => 'string', + 'description' => 'The estimated annual revenue bucket id.', + 'required' => true, + ], + 'go_live_timeframe' => [ + 'type' => 'string', + 'description' => 'The timeframe bucket for the estimated first live transaction.', + 'required' => true, + ], + 'url' => [ + 'type' => 'string', + 'description' => 'The URL of the store.', + 'required' => true, + ], + ], + ], + ], + ] + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/kyc/finalize', + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'finalize_embedded_kyc' ], + 'permission_callback' => [ $this, 'check_permission' ], + 'args' => [ + 'source' => [ + 'required' => false, + 'description' => 'The very first entry point the merchant entered our onboarding flow.', + 'type' => 'string', + ], + 'from' => [ + 'required' => false, + 'description' => 'The previous step in the onboarding flow leading the merchant to arrive at the current step.', + 'type' => 'string', + ], + ], + ] + ); + register_rest_route( $this->namespace, '/' . $this->rest_base . '/business_types', @@ -116,6 +196,72 @@ public function register_routes() { ); } + /** + * Create an account embedded KYC session via the API. + * + * @param WP_REST_Request $request Request object. + * + * @return WP_Error|WP_REST_Response + */ + public function get_embedded_kyc_session( WP_REST_Request $request ) { + $account_session = $this->onboarding_service->create_embedded_kyc_session( + ! empty( $request->get_param( 'self_assessment' ) ) ? wc_clean( wp_unslash( $request->get_param( 'self_assessment' ) ) ) : [], + ! empty( $request->get_param( 'progressive' ) ) && 'true' === $request->get_param( 'progressive' ), + ! empty( $request->get_param( 'collect_payout_requirements' ) ) && 'true' === $request->get_param( 'collect_payout_requirements' ) + ); + + if ( $account_session ) { + $account_session['locale'] = get_user_locale(); + } + + // Set the onboarding in progress option. + $this->onboarding_service->set_embedded_kyc_in_progress(); + + return rest_ensure_response( $account_session ); + } + + /** + * Finalize the embedded KYC session via the API. + * + * @param WP_REST_Request $request Request object. + * + * @return WP_Error|WP_HTTP_Response|WP_REST_Response + */ + public function finalize_embedded_kyc( WP_REST_Request $request ) { + $source = $request->get_param( 'source' ) ?? ''; + $from = $request->get_param( 'from' ) ?? ''; + $actioned_notes = WC_Payments_Onboarding_Service::get_actioned_notes(); + + // Call the API to finalize the onboarding. + try { + $response = $this->onboarding_service->finalize_embedded_kyc( + get_user_locale(), + $source, + $actioned_notes + ); + } catch ( Exception $e ) { + return new WP_Error( self::RESULT_BAD_REQUEST, $e->getMessage(), [ 'status' => 400 ] ); + } + + // Handle some post-onboarding tasks and get the redirect params. + $finalize = WC_Payments::get_account_service()->finalize_embedded_connection( + $response['mode'], + [ + 'promo' => $response['promotion_id'] ?? '', + 'from' => $from, + 'source' => $source, + ] + ); + + // Return the response, the client will handle the redirect. + return rest_ensure_response( + array_merge( + $response, + $finalize + ) + ); + } + /** * Get business types via API. * diff --git a/includes/admin/class-wc-rest-payments-settings-controller.php b/includes/admin/class-wc-rest-payments-settings-controller.php index ace444f5ae1..90fe8721443 100644 --- a/includes/admin/class-wc-rest-payments-settings-controller.php +++ b/includes/admin/class-wc-rest-payments-settings-controller.php @@ -514,7 +514,6 @@ public function get_settings(): WP_REST_Response { 'is_card_present_eligible' => $this->wcpay_gateway->is_card_present_eligible() && isset( WC()->payment_gateways()->get_available_payment_gateways()['cod'] ), 'is_woopay_enabled' => 'yes' === $this->wcpay_gateway->get_option( 'platform_checkout' ), 'show_woopay_incompatibility_notice' => get_option( 'woopay_invalid_extension_found', false ), - 'show_express_checkout_incompatibility_notice' => $this->should_show_express_checkout_incompatibility_notice(), 'woopay_custom_message' => $this->wcpay_gateway->get_option( 'platform_checkout_custom_message' ), 'woopay_store_logo' => $this->wcpay_gateway->get_option( 'platform_checkout_store_logo' ), 'woopay_enabled_locations' => $this->wcpay_gateway->get_option( 'platform_checkout_button_locations', array_keys( $wcpay_form_fields['payment_request_button_locations']['options'] ) ), @@ -1102,38 +1101,4 @@ private function update_reporting_export_language( WP_REST_Request $request ) { $this->wcpay_gateway->update_option( 'reporting_export_language', $reporting_export_language ); } - - /** - * Whether to show the express checkout incompatibility notice. - * - * @return bool - */ - private function should_show_express_checkout_incompatibility_notice() { - // Apply filters to empty arrays to check if any plugin is modifying the checkout fields. - $after_apply_billing = apply_filters( 'woocommerce_billing_fields', [], '' ); - $after_apply_shipping = apply_filters( 'woocommerce_shipping_fields', [], '' ); - $after_apply_checkout = array_filter( - apply_filters( - 'woocommerce_checkout_fields', - [ - 'billing' => [], - 'shipping' => [], - 'account' => [], - 'order' => [], - ] - ) - ); - // All the input values are empty, so if any of them is not empty, it means that the checkout fields are being modified. - $is_modifying_checkout_fields = ! empty( - array_filter( - [ - 'after_apply_billing' => $after_apply_billing, - 'after_apply_shipping' => $after_apply_shipping, - 'after_apply_checkout' => $after_apply_checkout, - ] - ) - ); - - return $is_modifying_checkout_fields; - } } diff --git a/includes/admin/class-wc-rest-woopay-session-controller.php b/includes/admin/class-wc-rest-woopay-session-controller.php index 6cbdff4d9a0..d5069412c6f 100644 --- a/includes/admin/class-wc-rest-woopay-session-controller.php +++ b/includes/admin/class-wc-rest-woopay-session-controller.php @@ -21,14 +21,14 @@ class WC_REST_WooPay_Session_Controller extends WP_REST_Controller { * * @var string */ - protected $namespace = 'wc/v3'; + protected $namespace = 'payments/woopay'; /** * Endpoint path. * * @var string */ - protected $rest_base = 'woopay/session'; + protected $rest_base = 'session'; /** * Configure REST API routes. diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 84620ca71c7..2f4afc40155 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -9,8 +9,6 @@ exit; // Exit if accessed directly. } -use Automattic\WooCommerce\Admin\Notes\DataStore; -use Automattic\WooCommerce\Admin\Notes\Note; use WCPay\Constants\Country_Code; use WCPay\Constants\Currency_Code; use WCPay\Core\Server\Request\Get_Account; @@ -30,6 +28,7 @@ class WC_Payments_Account { const ONBOARDING_DISABLED_TRANSIENT = 'wcpay_on_boarding_disabled'; const ONBOARDING_STARTED_TRANSIENT = 'wcpay_on_boarding_started'; const ONBOARDING_STATE_TRANSIENT = 'wcpay_stripe_onboarding_state'; + const EMBEDDED_KYC_IN_PROGRESS_OPTION = 'wcpay_onboarding_embedded_kyc_in_progress'; const ERROR_MESSAGE_TRANSIENT = 'wcpay_error_message'; const INSTANT_DEPOSITS_REMINDER_ACTION = 'wcpay_instant_deposit_reminder'; const TRACKS_EVENT_ACCOUNT_CONNECT_START = 'wcpay_account_connect_start'; @@ -61,11 +60,11 @@ class WC_Payments_Account { private $action_scheduler_service; /** - * WC_Payments_Session_Service instance for working with session information + * WC_Payments_Onboarding_Service instance for working with onboarding business logic * - * @var WC_Payments_Session_Service + * @var WC_Payments_Onboarding_Service */ - private $session_service; + private $onboarding_service; /** * WC_Payments_Redirect_Service instance for handling redirects business logic @@ -80,20 +79,20 @@ class WC_Payments_Account { * @param WC_Payments_API_Client $payments_api_client Payments API client. * @param Database_Cache $database_cache Database cache util. * @param WC_Payments_Action_Scheduler_Service $action_scheduler_service Action scheduler service. - * @param WC_Payments_Session_Service $session_service Session service. + * @param WC_Payments_Onboarding_Service $onboarding_service Onboarding service. * @param WC_Payments_Redirect_Service $redirect_service Redirect service. */ public function __construct( WC_Payments_API_Client $payments_api_client, Database_Cache $database_cache, WC_Payments_Action_Scheduler_Service $action_scheduler_service, - WC_Payments_Session_Service $session_service, + WC_Payments_Onboarding_Service $onboarding_service, WC_Payments_Redirect_Service $redirect_service ) { $this->payments_api_client = $payments_api_client; $this->database_cache = $database_cache; $this->action_scheduler_service = $action_scheduler_service; - $this->session_service = $session_service; + $this->onboarding_service = $onboarding_service; $this->redirect_service = $redirect_service; } @@ -200,13 +199,12 @@ public function has_account_data(): bool { * Checks if the account is connected, assumes the value of $on_error on server error. * * @param bool $on_error Value to return on server error, defaults to false. - * @param bool $force_refresh Force refresh account data cache. * * @return bool True if the account is connected, false otherwise, $on_error on error. */ - public function is_stripe_connected( bool $on_error = false, bool $force_refresh = false ): bool { + public function is_stripe_connected( bool $on_error = false ): bool { try { - return $this->try_is_stripe_connected( $force_refresh ); + return $this->try_is_stripe_connected(); } catch ( Exception $e ) { return $on_error; } @@ -215,13 +213,11 @@ public function is_stripe_connected( bool $on_error = false, bool $force_refresh /** * Checks if the account is connected, throws on server error. * - * @param bool $force_refresh Force refresh account data cache. - * * @return bool True if the account is connected, false otherwise. * @throws Exception Throws exception when unable to detect connection status. */ - public function try_is_stripe_connected( bool $force_refresh = false ): bool { - $account = $this->get_cached_account_data( $force_refresh ); + public function try_is_stripe_connected(): bool { + $account = $this->get_cached_account_data(); if ( false === $account ) { throw new Exception( esc_html__( 'Failed to detect connection status', 'woocommerce-payments' ) ); } @@ -903,8 +899,9 @@ public function maybe_redirect_from_onboarding_wizard_page(): bool { } /** - * Redirects connect page (payments/connect) to the overview page for stores that - * have a working Jetpack connection and a valid Stripe account. + * Maybe redirects the connect page (payments/connect) + * + * We redirect to the overview page for stores that have a working Jetpack connection and a valid Stripe account. * * Note: Connect _page_ links are not the same as connect links. * Connect links are used to start/re-start/continue the onboarding flow and they are independent of @@ -932,12 +929,53 @@ public function maybe_redirect_from_connect_page(): bool { return false; } + // There are certain cases where it is best to refresh the account data + // to be sure we are dealing with the current account state on the Connect page: + // - When the merchant is coming from the onboarding wizard it is best to refresh the account data because + // the merchant might have started the embedded Stripe KYC. + // - When the merchant is coming from the embedded KYC, definitely refresh the account data. + // The account data shouldn't be refreshed with force disconnected option enabled. + if ( ! WC_Payments_Utils::force_disconnected_enabled() + && in_array( + WC_Payments_Onboarding_Service::get_from(), + [ + WC_Payments_Onboarding_Service::FROM_ONBOARDING_WIZARD, + WC_Payments_Onboarding_Service::FROM_ONBOARDING_KYC, + ], + true + ) ) { + + $this->refresh_account_data(); + } + // If everything is in good working condition, redirect to Payments Overview page. if ( $this->has_working_jetpack_connection() && $this->is_stripe_account_valid() ) { $this->redirect_service->redirect_to_overview_page( WC_Payments_Onboarding_Service::FROM_CONNECT_PAGE ); return true; } + // Determine from where the merchant was directed to the Connect page. + $from = WC_Payments_Onboarding_Service::get_from(); + + // If the user came from the core Payments task list item, + // we run an experiment to skip the Connect page + // and go directly to the Jetpack connection flow and/or onboarding wizard. + if ( WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK === $from + && WC_Payments_Utils::is_in_core_payments_task_onboarding_flow_treatment_mode() ) { + + // We use a connect link to allow our logic to determine what comes next: + // the Jetpack connection setup and/or onboarding wizard (MOX). + $this->redirect_service->redirect_to_wcpay_connect( + // The next step should treat the merchant as coming from the Payments task list item, + // not the Connect page. + WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK, + [ + 'source' => WC_Payments_Onboarding_Service::get_source(), + ] + ); + return true; + } + return false; } @@ -1174,6 +1212,7 @@ public function maybe_handle_onboarding() { || ( WC_Payments_Onboarding_Service::FROM_STRIPE === $from && ! empty( $_GET['wcpay-connection-error'] ) ) ) { delete_transient( self::ONBOARDING_STATE_TRANSIENT ); + delete_option( self::EMBEDDED_KYC_IN_PROGRESS_OPTION ); } // Make changes to account data as instructed by action GET params. @@ -1258,7 +1297,9 @@ public function maybe_handle_onboarding() { 'WooPayments' ), WC_Payments_Onboarding_Service::FROM_WPCOM_CONNECTION, - [ 'source' => $onboarding_source ] + [ + 'source' => $onboarding_source, + ] ); return; @@ -1299,11 +1340,18 @@ public function maybe_handle_onboarding() { $from, [ WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_SETTINGS, - WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK, WC_Payments_Onboarding_Service::FROM_STRIPE, ], true ) + /** + * We are running an experiment to skip the Connect page for Payments Task flows. + * Only redirect to the Connect page if the user is not in the experiment's treatment mode. + * + * @see self::maybe_redirect_from_connect_page() + */ + || ( WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK === $from + && ! WC_Payments_Utils::is_in_core_payments_task_onboarding_flow_treatment_mode() ) // This is a weird case, but it is best to handle it. || ( WC_Payments_Onboarding_Service::FROM_ONBOARDING_WIZARD === $from && ! $this->has_working_jetpack_connection() ) ) { @@ -1360,7 +1408,9 @@ public function maybe_handle_onboarding() { /* translators: %s: error message. */ sprintf( __( 'There was a problem connecting your store to WordPress.com: "%s"', 'woocommerce-payments' ), $e->getMessage() ), WC_Payments_Onboarding_Service::FROM_WPCOM_CONNECTION, - [ 'source' => $onboarding_source ] + [ + 'source' => $onboarding_source, + ] ); return; } @@ -1376,7 +1426,9 @@ public function maybe_handle_onboarding() { // When we redirect to the onboarding wizard, we carry over the `from`, if we have it. // This is because there is no interim step between the user clicking the connect link and the onboarding wizard. ! empty( $from ) ? $from : $next_step_from, - [ 'source' => $onboarding_source ] + [ + 'source' => $onboarding_source, + ] ); return; } @@ -1400,6 +1452,7 @@ public function maybe_handle_onboarding() { if ( $create_test_drive_account ) { // Since there should be no Stripe KYC needed, make sure we start with a clean state. delete_transient( self::ONBOARDING_STATE_TRANSIENT ); + delete_option( self::EMBEDDED_KYC_IN_PROGRESS_OPTION ); // If we have the auto_start_test_drive_onboarding flag, we redirect to the Connect page // to let the JS logic take control and orchestrate things. @@ -1482,7 +1535,7 @@ public function maybe_handle_onboarding() { if ( $create_test_drive_account && ! empty( $redirect_to ) ) { wp_send_json_success( [ 'redirect_to' => $redirect_to ] ); } else { - // Redirect the user to where our Stripe onboarding instructed. + // Redirect the user to where our Stripe onboarding instructed (or to our own embedded Stripe KYC). $this->redirect_service->redirect_to( $redirect_to ); } } catch ( API_Exception $e ) { @@ -1499,7 +1552,9 @@ public function maybe_handle_onboarding() { 'WooPayments' ), null, - [ 'source' => $onboarding_source ] + [ + 'source' => $onboarding_source, + ] ); return; } @@ -1561,6 +1616,7 @@ private function cleanup_on_account_reset() { // Discard any ongoing onboarding session. delete_transient( self::ONBOARDING_STATE_TRANSIENT ); delete_transient( self::ONBOARDING_STARTED_TRANSIENT ); + delete_option( self::EMBEDDED_KYC_IN_PROGRESS_OPTION ); delete_transient( 'woopay_enabled_by_default' ); // Clear the cache to avoid stale data. @@ -1744,6 +1800,24 @@ private function get_onboarding_return_url( string $wcpay_connect_from ): string } } + /** + * Get the URL to the embedded onboarding KYC page. + * + * @param array $additional_args Additional query args to add to the URL. + * + * @return string + */ + private function get_onboarding_kyc_url( array $additional_args = [] ): string { + $params = [ + 'page' => 'wc-admin', + 'path' => '/payments/onboarding/kyc', + ]; + + $params = array_merge( $params, $additional_args ); + + return admin_url( add_query_arg( $params, 'admin.php' ) ); + } + /** * Initializes the onboarding flow by fetching the URL from the API and redirecting to it. * @@ -1775,83 +1849,29 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne WC_Payments_Onboarding_Service::set_onboarding_eligibility_modal_dismissed(); } + // If we are in the middle of an embedded onboarding, go to the KYC page. + // In this case, we don't need to generate a return URL from Stripe, and we + // can rely on the JS logic to generate the session. + // Currently under feature flag. + if ( WC_Payments_Features::is_embedded_kyc_enabled() && $this->onboarding_service->is_embedded_kyc_in_progress() ) { + // We want to carry over the connect link from value because with embedded KYC + // there is no interim step for the user. + $additional_args['from'] = WC_Payments_Onboarding_Service::get_from(); + + return $this->get_onboarding_kyc_url( $additional_args ); + } + + // Else, go on with the normal onboarding redirect logic. $return_url = $this->get_onboarding_return_url( $wcpay_connect_from ); if ( ! empty( $additional_args ) ) { $return_url = add_query_arg( $additional_args, $return_url ); } - $home_url = get_home_url(); - // If the site is running on localhost, use a bogus URL. This is to avoid Stripe's errors. - // wp_http_validate_url does not check that, unfortunately. - $home_is_localhost = 'localhost' === wp_parse_url( $home_url, PHP_URL_HOST ); - $fallback_url = ( 'live' !== $setup_mode || $home_is_localhost ) ? 'https://wcpay.test' : null; - - $current_user = get_userdata( get_current_user_id() ); - - // The general account data. - $account_data = [ - 'setup_mode' => $setup_mode, - // We use the store base country to create a customized account. - 'country' => WC()->countries->get_base_country() ?? null, - 'url' => ! $home_is_localhost && wp_http_validate_url( $home_url ) ? $home_url : $fallback_url, - 'business_name' => get_bloginfo( 'name' ), - ]; - - // Gather all the account data depending on the request context. - // Onboarding self-assessment data. $self_assessment_data = isset( $_GET['self_assessment'] ) ? wc_clean( wp_unslash( $_GET['self_assessment'] ) ) : []; - if ( ! empty( $self_assessment_data ) ) { - $business_type = $self_assessment_data['business_type'] ?? null; - $account_data = WC_Payments_Utils::array_merge_recursive_distinct( - $account_data, - [ - // Overwrite the country if the merchant chose a different one than the Woo base location. - 'country' => $self_assessment_data['country'] ?? null, - 'email' => $self_assessment_data['email'] ?? null, - 'business_name' => $self_assessment_data['business_name'] ?? null, - 'url' => $self_assessment_data['url'] ?? null, - 'mcc' => $self_assessment_data['mcc'] ?? null, - 'business_type' => $business_type, - 'company' => [ - 'structure' => 'company' === $business_type ? ( $self_assessment_data['company']['structure'] ?? null ) : null, - ], - 'individual' => [ - 'first_name' => $self_assessment_data['individual']['first_name'] ?? null, - 'last_name' => $self_assessment_data['individual']['last_name'] ?? null, - 'phone' => $self_assessment_data['phone'] ?? null, - ], - 'store' => [ - 'annual_revenue' => $self_assessment_data['annual_revenue'] ?? null, - 'go_live_timeframe' => $self_assessment_data['go_live_timeframe'] ?? null, - ], - ] - ); - } elseif ( 'test_drive' === $setup_mode ) { - $account_data = WC_Payments_Utils::array_merge_recursive_distinct( - $account_data, - [ - 'individual' => [ - 'first_name' => $current_user->first_name ?? null, - 'last_name' => $current_user->last_name ?? null, - ], - ] - ); - + if ( 'test_drive' === $setup_mode ) { // If we get to the overview page, we want to show the success message. $return_url = add_query_arg( 'wcpay-sandbox-success', 'true', $return_url ); } elseif ( 'test' === $setup_mode ) { - $account_data = WC_Payments_Utils::array_merge_recursive_distinct( - $account_data, - [ - 'business_type' => 'individual', - 'mcc' => '5734', - 'individual' => [ - 'first_name' => $current_user->first_name ?? null, - 'last_name' => $current_user->last_name ?? null, - ], - ] - ); - // If we get to the overview page, we want to show the success message. $return_url = add_query_arg( 'wcpay-sandbox-success', 'true', $return_url ); } @@ -1861,7 +1881,8 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne 'site_locale' => get_locale(), ]; - $user_data = $this->get_onboarding_user_data(); + $user_data = $this->onboarding_service->get_onboarding_user_data(); + $account_data = $this->onboarding_service->get_account_data( $setup_mode, $self_assessment_data ); $onboarding_data = $this->payments_api_client->get_onboarding_data( 'live' === $setup_mode, @@ -1869,7 +1890,7 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne $site_data, WC_Payments_Utils::array_filter_recursive( $user_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. WC_Payments_Utils::array_filter_recursive( $account_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. - $this->get_actioned_notes(), + WC_Payments_Onboarding_Service::get_actioned_notes(), $progressive, $collect_payout_requirements ); @@ -1888,6 +1909,7 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne // Clean up any existing onboarding state. delete_transient( self::ONBOARDING_STATE_TRANSIENT ); + delete_option( self::EMBEDDED_KYC_IN_PROGRESS_OPTION ); return add_query_arg( [ 'wcpay-connection-success' => '1' ], @@ -1920,6 +1942,50 @@ public function maybe_activate_woopay() { } } + /** + * Handle the finalization of an embedded onboarding. This includes updating the cache, setting the gateway mode, + * tracking the event, and redirecting the user to the overview page. + * + * @param string $mode The mode in which the account was created. Either 'test' or 'live'. + * @param array $additional_args Additional query args to add to the redirect URLs. + * + * @return array Returns whether the operation was successful, along with the URL params to handle the redirect. + */ + public function finalize_embedded_connection( string $mode, array $additional_args = [] ): array { + // Clear the account cache. + $this->clear_cache(); + + // Set the gateway options. + $gateway = WC_Payments::get_gateway(); + $gateway->update_option( 'enabled', 'yes' ); + $gateway->update_option( 'test_mode', 'live' !== $mode ? 'yes' : 'no' ); + + // Store a state after completing KYC for tracks. This is stored temporarily in option because + // user might not have agreed to TOS yet. + update_option( '_wcpay_onboarding_stripe_connected', [ 'is_existing_stripe_account' => false ] ); + + // Track account connection finish. + $event_properties = [ + 'mode' => 'test' === $mode ? 'test' : 'live', + 'incentive' => ! empty( $additional_args['promo'] ) ? sanitize_text_field( $additional_args['promo'] ) : '', + 'from' => ! empty( $additional_args['from'] ) ? sanitize_text_field( $additional_args['from'] ) : '', + 'source' => ! empty( $additional_args['source'] ) ? sanitize_text_field( $additional_args['source'] ) : '', + ]; + + $this->tracks_event( + self::TRACKS_EVENT_ACCOUNT_CONNECT_FINISHED, + $event_properties + ); + + $params = $additional_args; + + $params['wcpay-connection-success'] = '1'; + return [ + 'success' => true, + 'params' => $params, + ]; + } + /** * Once the API redirects back to the site after the onboarding flow, verifies the parameters and stores the data. * @@ -1956,8 +2022,8 @@ private function finalize_connection( string $state, string $mode, array $additi update_option( '_wcpay_onboarding_stripe_connected', [ 'is_existing_stripe_account' => false ] ); // Track account connection finish. - $incentive_id = ! empty( $_GET['promo'] ) ? sanitize_text_field( wp_unslash( $_GET['promo'] ) ) : ''; - $event_properties = [ + $incentive_id = ! empty( $_GET['promo'] ) ? sanitize_text_field( wp_unslash( $_GET['promo'] ) ) : ''; + $tracks_props = [ 'incentive' => $incentive_id, 'mode' => 'live' !== $mode ? 'test' : 'live', 'from' => $additional_args['from'] ?? '', @@ -1965,7 +2031,7 @@ private function finalize_connection( string $state, string $mode, array $additi ]; $this->tracks_event( self::TRACKS_EVENT_ACCOUNT_CONNECT_FINISHED, - $event_properties + $tracks_props ); $params = $additional_args; @@ -2204,59 +2270,6 @@ public function get_latest_tos_agreement() { : null; } - /** - * Returns an array containing the names of all the WCPay related notes that have been actioned. - * - * @return array - */ - private function get_actioned_notes(): array { - $wcpay_note_names = []; - - try { - /** - * Data Store for admin notes - * - * @var DataStore $data_store - */ - $data_store = WC_Data_Store::load( 'admin-note' ); - } catch ( Exception $e ) { - // Don't stop the on-boarding process if something goes wrong here. Log the error and return the empty array - // of actioned notes. - Logger::error( $e ); - return $wcpay_note_names; - } - - // Fetch the last 10 actioned wcpay-promo admin notifications. - $add_like_clause = function ( $where_clause ) { - return $where_clause . " AND name like 'wcpay-promo-%'"; - }; - - add_filter( 'woocommerce_note_where_clauses', $add_like_clause ); - - $wcpay_promo_notes = $data_store->get_notes( - [ - 'status' => [ Note::E_WC_ADMIN_NOTE_ACTIONED ], - 'is_deleted' => false, - 'per_page' => 10, - ] - ); - - remove_filter( 'woocommerce_note_where_clauses', $add_like_clause ); - - // If we didn't get an array back from the data store, return an empty array of results. - if ( ! is_array( $wcpay_promo_notes ) ) { - return $wcpay_note_names; - } - - // Copy the name of each note into the results. - foreach ( (array) $wcpay_promo_notes as $wcpay_note ) { - $note = new Note( $wcpay_note->note_id ); - $wcpay_note_names[] = $note->get_name(); - } - - return $wcpay_note_names; - } - /** * Gets the account country. * @@ -2455,24 +2468,4 @@ public function get_lifetime_total_payment_volume(): int { $account = $this->get_cached_account_data(); return (int) ! empty( $account ) && isset( $account['lifetime_total_payment_volume'] ) ? $account['lifetime_total_payment_volume'] : 0; } - - /** - * Get user data to send to the onboarding flow. - * - * @return array The user data. - */ - private function get_onboarding_user_data(): array { - return [ - 'user_id' => get_current_user_id(), - 'sift_session_id' => $this->session_service->get_sift_session_id(), - 'ip_address' => \WC_Geolocation::get_ip_address(), - 'browser' => [ - 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '', - 'accept_language' => isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : '', - 'content_language' => empty( get_user_locale() ) ? 'en-US' : str_replace( '_', '-', get_user_locale() ), - ], - 'referer' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '', - 'onboarding_source' => WC_Payments_Onboarding_Service::get_source(), - ]; - } } diff --git a/includes/class-wc-payments-apple-pay-registration.php b/includes/class-wc-payments-apple-pay-registration.php index cb58eaa6796..a9ffdca6c9b 100644 --- a/includes/class-wc-payments-apple-pay-registration.php +++ b/includes/class-wc-payments-apple-pay-registration.php @@ -95,7 +95,6 @@ public function init() { add_action( 'parse_request', [ $this, 'parse_domain_association_request' ], 10, 1 ); add_action( 'woocommerce_woocommerce_payments_admin_notices', [ $this, 'display_error_notice' ] ); - add_action( 'woocommerce_woocommerce_payments_admin_notices', [ $this, 'display_live_account_notice' ] ); add_action( 'add_option_woocommerce_woocommerce_payments_settings', [ $this, 'verify_domain_on_new_settings' ], 10, 2 ); add_action( 'update_option_woocommerce_woocommerce_payments_settings', [ $this, 'verify_domain_on_updated_settings' ], 10, 2 ); } @@ -353,30 +352,6 @@ public function verify_domain_on_updated_settings( $prev_settings, $settings ) { } } - /** - * Display warning notice explaining that the domain can't be registered without a live account. - */ - public function display_live_account_notice() { - if ( ! $this->is_enabled() || $this->account->get_is_live() ) { - return; - } - - ?> -
-

- - -

-
- gateway->get_account_statement_descriptor(); - $payment_fields['addPaymentReturnURL'] = wc_get_account_endpoint_url( 'payment-methods' ); - $payment_fields['gatewayId'] = WC_Payment_Gateway_WCPay::GATEWAY_ID; - $payment_fields['isCheckout'] = is_checkout(); - $payment_fields['paymentMethodsConfig'] = $this->get_enabled_payment_method_config(); - $payment_fields['testMode'] = WC_Payments::mode()->is_test(); - $payment_fields['upeAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_APPEARANCE_TRANSIENT ); - $payment_fields['upeAddPaymentMethodAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_ADD_PAYMENT_METHOD_APPEARANCE_TRANSIENT ); - $payment_fields['upeAddPaymentMethodAppearanceTheme'] = get_transient( WC_Payment_Gateway_WCPay::UPE_ADD_PAYMENT_METHOD_APPEARANCE_THEME_TRANSIENT ); - $payment_fields['upeBnplProductPageAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_PRODUCT_PAGE_APPEARANCE_TRANSIENT ); - $payment_fields['upeBnplProductPageAppearanceTheme'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_PRODUCT_PAGE_APPEARANCE_THEME_TRANSIENT ); - $payment_fields['upeBnplClassicCartAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_CLASSIC_CART_APPEARANCE_TRANSIENT ); - $payment_fields['upeBnplClassicCartAppearanceTheme'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_CLASSIC_CART_APPEARANCE_THEME_TRANSIENT ); - $payment_fields['upeBnplCartBlockAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_CART_BLOCK_APPEARANCE_TRANSIENT ); - $payment_fields['upeBnplCartBlockAppearanceTheme'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_CART_BLOCK_APPEARANCE_THEME_TRANSIENT ); - $payment_fields['wcBlocksUPEAppearance'] = get_transient( WC_Payment_Gateway_WCPay::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT ); - $payment_fields['wcBlocksUPEAppearanceTheme'] = get_transient( WC_Payment_Gateway_WCPay::WC_BLOCKS_UPE_APPEARANCE_THEME_TRANSIENT ); - $payment_fields['cartContainsSubscription'] = $this->gateway->is_subscription_item_in_cart(); - $payment_fields['currency'] = get_woocommerce_currency(); - $cart_total = ( WC()->cart ? WC()->cart->get_total( '' ) : 0 ); - $payment_fields['cartTotal'] = WC_Payments_Utils::prepare_amount( $cart_total, get_woocommerce_currency() ); + $payment_fields['accountDescriptor'] = $this->gateway->get_account_statement_descriptor(); + $payment_fields['addPaymentReturnURL'] = wc_get_account_endpoint_url( 'payment-methods' ); + $payment_fields['gatewayId'] = WC_Payment_Gateway_WCPay::GATEWAY_ID; + $payment_fields['isCheckout'] = is_checkout(); + $payment_fields['paymentMethodsConfig'] = $this->get_enabled_payment_method_config(); + $payment_fields['testMode'] = WC_Payments::mode()->is_test(); + $payment_fields['upeAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_APPEARANCE_TRANSIENT ); + $payment_fields['upeAddPaymentMethodAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_ADD_PAYMENT_METHOD_APPEARANCE_TRANSIENT ); + $payment_fields['upeBnplProductPageAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_PRODUCT_PAGE_APPEARANCE_TRANSIENT ); + $payment_fields['upeBnplClassicCartAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_CLASSIC_CART_APPEARANCE_TRANSIENT ); + $payment_fields['upeBnplCartBlockAppearance'] = get_transient( WC_Payment_Gateway_WCPay::UPE_BNPL_CART_BLOCK_APPEARANCE_TRANSIENT ); + $payment_fields['wcBlocksUPEAppearance'] = get_transient( WC_Payment_Gateway_WCPay::WC_BLOCKS_UPE_APPEARANCE_TRANSIENT ); + $payment_fields['wcBlocksUPEAppearanceTheme'] = get_transient( WC_Payment_Gateway_WCPay::WC_BLOCKS_UPE_APPEARANCE_THEME_TRANSIENT ); + $payment_fields['cartContainsSubscription'] = $this->gateway->is_subscription_item_in_cart(); + $payment_fields['currency'] = get_woocommerce_currency(); + $cart_total = ( WC()->cart ? WC()->cart->get_total( '' ) : 0 ); + $payment_fields['cartTotal'] = WC_Payments_Utils::prepare_amount( $cart_total, get_woocommerce_currency() ); $enabled_billing_fields = []; foreach ( WC()->checkout()->get_checkout_fields( 'billing' ) as $billing_field => $billing_field_options ) { @@ -331,8 +327,9 @@ public function get_enabled_payment_method_config() { /* translators: link to Stripe testing page */ $payment_method->get_testing_instructions(), [ - 'strong' => '', 'a' => '', + 'strong' => '', + 'number' => ' or refer to our testing guide.', 'forceNetworkSavedCards' => false, ], 'link' => [ @@ -396,7 +395,8 @@ public function test_link_payment_method_provided_when_card_enabled() { 'testingInstructions' => '', 'forceNetworkSavedCards' => false, ], - ] + ], + $this->system_under_test->get_payment_fields_js_config()['paymentMethodsConfig'] ); } diff --git a/tests/unit/test-class-wc-payments-features.php b/tests/unit/test-class-wc-payments-features.php index 217bb7be513..e9405375d8f 100644 --- a/tests/unit/test-class-wc-payments-features.php +++ b/tests/unit/test-class-wc-payments-features.php @@ -30,6 +30,7 @@ class WC_Payments_Features_Test extends WCPAY_UnitTestCase { '_wcpay_feature_documents' => 'documents', '_wcpay_feature_auth_and_capture' => 'isAuthAndCaptureEnabled', '_wcpay_feature_stripe_ece' => 'isStripeEceEnabled', + '_wcpay_feature_embedded_kyc' => 'isEmbeddedKycEnabled', ]; public function set_up() { @@ -300,6 +301,35 @@ public function test_is_frt_review_feature_active_returns_false_when_flag_is_not $this->assertFalse( WC_Payments_Features::is_frt_review_feature_active() ); } + public function test_is_embedded_kyc_enabled_returns_true() { + add_filter( + 'pre_option_' . WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, + function ( $pre_option, $option, $default ) { + return '1'; + }, + 10, + 3 + ); + $this->assertTrue( WC_Payments_Features::is_embedded_kyc_enabled() ); + } + + public function test_is_embedded_kyc_enabled_returns_false_when_flag_is_false() { + add_filter( + 'pre_option_' . WC_Payments_Features::EMBEDDED_KYC_FLAG_NAME, + function ( $pre_option, $option, $default ) { + return '0'; + }, + 10, + 3 + ); + $this->assertFalse( WC_Payments_Features::is_embedded_kyc_enabled() ); + $this->assertArrayNotHasKey( 'isEmbeddedKycEnabled', WC_Payments_Features::to_array() ); + } + + public function test_is_embedded_kyc_enabled_returns_false_when_flag_is_not_set() { + $this->assertFalse( WC_Payments_Features::is_embedded_kyc_enabled() ); + } + private function setup_enabled_flags( array $enabled_flags ) { foreach ( array_keys( self::FLAG_OPTION_NAME_TO_FRONTEND_KEY_MAPPING ) as $flag ) { add_filter( diff --git a/tests/unit/test-class-wc-payments-onboarding-service.php b/tests/unit/test-class-wc-payments-onboarding-service.php index 7ec162205e4..a8d9686f763 100644 --- a/tests/unit/test-class-wc-payments-onboarding-service.php +++ b/tests/unit/test-class-wc-payments-onboarding-service.php @@ -34,6 +34,13 @@ class WC_Payments_Onboarding_Service_Test extends WCPAY_UnitTestCase { */ private $mock_database_cache; + /** + * Mock WC_Payments_Session_Service + * + * @var MockObject + */ + private $mock_session_service; + /** * Example business types array. * @@ -129,10 +136,11 @@ class WC_Payments_Onboarding_Service_Test extends WCPAY_UnitTestCase { public function set_up() { parent::set_up(); - $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class ); - $this->mock_database_cache = $this->createMock( Database_Cache::class ); + $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + $this->mock_database_cache = $this->createMock( Database_Cache::class ); + $this->mock_session_service = $this->createMock( WC_Payments_Session_Service::class ); - $this->onboarding_service = new WC_Payments_Onboarding_Service( $this->mock_api_client, $this->mock_database_cache ); + $this->onboarding_service = new WC_Payments_Onboarding_Service( $this->mock_api_client, $this->mock_database_cache, $this->mock_session_service ); $this->onboarding_service->init_hooks(); } @@ -202,6 +210,18 @@ public function test_set_test_mode() { delete_option( WC_Payments_Onboarding_Service::TEST_MODE_OPTION ); } + public function test_is_embedded_kyc_in_progress() { + $this->assertFalse( $this->onboarding_service->is_embedded_kyc_in_progress() ); + + $this->onboarding_service->set_embedded_kyc_in_progress(); + + $this->assertTrue( $this->onboarding_service->is_embedded_kyc_in_progress() ); + + $this->onboarding_service->clear_embedded_kyc_in_progress(); + + $this->assertFalse( $this->onboarding_service->is_embedded_kyc_in_progress() ); + } + /** * @dataProvider data_get_from */ diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index de372ddc989..db1c564d44c 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -430,6 +430,7 @@ public function test_get_shipping_options_returns_chosen_option() { [ 'label' => 'Shipping', 'amount' => $flat_rate['amount'], + 'key' => 'total_shipping', ], ]; diff --git a/tests/unit/test-class-wc-payments-utils.php b/tests/unit/test-class-wc-payments-utils.php index 08a9420d674..a7aba06f9ab 100644 --- a/tests/unit/test-class-wc-payments-utils.php +++ b/tests/unit/test-class-wc-payments-utils.php @@ -931,6 +931,12 @@ public function test_get_cached_minimum_amount_returns_null_without_cache() { $this->assertNull( $result ); } + public function test_get_cached_minimum_amount_returns_amount_fallbacking_from_stripe_list() { + delete_transient( 'wcpay_minimum_amount_usd' ); + $result = WC_Payments_Utils::get_cached_minimum_amount( 'usd', true ); + $this->assertSame( 50, $result ); + } + public function test_get_last_refund_from_order_id_returns_correct_refund() { $order = WC_Helper_Order::create_order(); $refund_1 = wc_create_refund( [ 'order_id' => $order->get_id() ] ); diff --git a/webpack/shared.js b/webpack/shared.js index 953830ba44f..7ef039967cd 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -41,6 +41,7 @@ module.exports = { 'product-details': './client/product-details/index.js', 'cart-block': './client/cart/blocks/index.js', 'plugins-page': './client/plugins-page/index.js', + 'frontend-tracks': './client/frontend-tracks/index.js', }, // Override webpack public path dynamically on every entry. // Required for chunks loading to work on sites with JS concatenation. diff --git a/woocommerce-payments.php b/woocommerce-payments.php index 16cecd888db..149100add8e 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -11,7 +11,7 @@ * WC tested up to: 9.2.0 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 8.1.1 + * Version: 8.2.0 * Requires Plugins: woocommerce * * @package WooCommerce\Payments @@ -43,7 +43,7 @@ function wcpay_activated() { // Only redirect to onboarding when activated on its own. Either with a link... isset( $_GET['action'] ) && 'activate' === $_GET['action'] // phpcs:ignore WordPress.Security.NonceVerification // ...or with a bulk action. - || isset( $_POST['checked'] ) && is_array( $_POST['checked'] ) && 1 === count( $_POST['checked'] ) // phpcs:ignore WordPress.Security.NonceVerification + || isset( $_POST['checked'] ) && is_array( $_POST['checked'] ) && 1 === count( $_POST['checked'] ) // phpcs:ignore WordPress.Security.NonceVerification, Generic.CodeAnalysis.RequireExplicitBooleanOperatorPrecedence.MissingParentheses ) { update_option( 'wcpay_should_redirect_to_onboarding', true ); }