diff --git a/projects/packages/my-jetpack/_inc/components/welcome-flow/ConnectionStep.tsx b/projects/packages/my-jetpack/_inc/components/welcome-flow/ConnectionStep.tsx index fe41b8d1f136e..d62acd4a7a27d 100644 --- a/projects/packages/my-jetpack/_inc/components/welcome-flow/ConnectionStep.tsx +++ b/projects/packages/my-jetpack/_inc/components/welcome-flow/ConnectionStep.tsx @@ -1,45 +1,43 @@ -import { Col, Button, Text, TermsOfService } from '@automattic/jetpack-components'; +import { Col, Button, Text, TermsOfService, getRedirectUrl } from '@automattic/jetpack-components'; import { initializeExPlat, loadExperimentAssignment } from '@automattic/jetpack-explat'; import { __ } from '@wordpress/i18n'; import { useCallback, useContext } from 'react'; import { NoticeContext } from '../../context/notices/noticeContext'; import { NOTICE_SITE_CONNECTED } from '../../context/notices/noticeTemplates'; import useProductsByOwnership from '../../data/products/use-products-by-ownership'; +import { getMyJetpackWindowInitialState } from '../../data/utils/get-my-jetpack-window-state'; import useAnalytics from '../../hooks/use-analytics'; import sideloadTracks from '../../utils/side-load-tracks'; import styles from './style.module.scss'; -import type { WelcomeFlowExperiment } from '.'; -import type { Dispatch, SetStateAction } from 'react'; type ConnectionStepProps = { onActivateSite: ( e?: Event ) => Promise< void >; - onUpdateWelcomeFlowExperiment: Dispatch< SetStateAction< WelcomeFlowExperiment > >; isActivating: boolean; }; /** * Component that renders the Welcome banner on My Jetpack. * - * @param {object} props - ConnectioStepProps - * @param {Function} props.onActivateSite - Alias for handleRegisterSite - * @param {Function} props.onUpdateWelcomeFlowExperiment - Updating the welcomeFlowExperiment state - * @param {boolean} props.isActivating - Alias for siteIsRegistering + * @param {object} props - ConnectioStepProps + * @param {Function} props.onActivateSite - Alias for handleRegisterSite + * @param {boolean} props.isActivating - Alias for siteIsRegistering * @return {object} The ConnectionStep component. */ -const ConnectionStep = ( { - onActivateSite, - onUpdateWelcomeFlowExperiment, - isActivating, -}: ConnectionStepProps ) => { +const ConnectionStep = ( { onActivateSite, isActivating }: ConnectionStepProps ) => { const { recordEvent } = useAnalytics(); const { setNotice, resetNotice } = useContext( NoticeContext ); + const { purchaseToken, siteUrl, adminUrl } = getMyJetpackWindowInitialState(); + const redirectUri = `?redirect_uri=${ encodeURIComponent( window.location.href ) }`; + const connectAfterCheckoutUrl = `&connect_after_checkout=true&from_site_slug=${ siteUrl }&admin_url=${ adminUrl }`; + const query = `${ redirectUri }${ purchaseToken }${ connectAfterCheckoutUrl }`; + const jetpackPlansPath = getRedirectUrl( 'jetpack-plans', { query } ); + const activationButtonLabel = __( 'Activate Jetpack in one click', 'jetpack-my-jetpack' ); const { refetch: refetchOwnershipData } = useProductsByOwnership(); const onConnectSiteClick = useCallback( async () => { recordEvent( 'jetpack_myjetpack_welcome_banner_connect_site_click' ); - onUpdateWelcomeFlowExperiment( state => ( { ...state, isLoading: true } ) ); await onActivateSite(); recordEvent( 'jetpack_myjetpack_welcome_banner_connect_site_success' ); @@ -50,23 +48,20 @@ const ConnectionStep = ( { initializeExPlat(); const { variationName } = await loadExperimentAssignment( - 'jetpack_my_jetpack_welcome_flow_display_default_recommendations_upfront_202410' + 'jetpack_my_jetpack_recommendations_pricing_page_202411' ); - onUpdateWelcomeFlowExperiment( state => ( { - ...state, - variation: ( variationName ?? 'control' ) as WelcomeFlowExperiment[ 'variation' ], // casting to 'control' or 'treatment' - } ) ); + if ( variationName === 'treatment' ) { + window.location.href = jetpackPlansPath; + } } finally { resetNotice(); setNotice( NOTICE_SITE_CONNECTED, resetNotice ); refetchOwnershipData(); - - onUpdateWelcomeFlowExperiment( state => ( { ...state, isLoading: false } ) ); } }, [ + jetpackPlansPath, onActivateSite, - onUpdateWelcomeFlowExperiment, recordEvent, refetchOwnershipData, resetNotice, diff --git a/projects/packages/my-jetpack/_inc/components/welcome-flow/index.tsx b/projects/packages/my-jetpack/_inc/components/welcome-flow/index.tsx index 430c426c9514d..9fc11bfa3f401 100644 --- a/projects/packages/my-jetpack/_inc/components/welcome-flow/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/welcome-flow/index.tsx @@ -25,11 +25,7 @@ interface Props extends PropsWithChildren { setWelcomeFlowExperiment: React.Dispatch< React.SetStateAction< WelcomeFlowExperiment > >; } -const WelcomeFlow: FC< Props > = ( { - welcomeFlowExperiment, - setWelcomeFlowExperiment, - children, -} ) => { +const WelcomeFlow: FC< Props > = ( { children } ) => { const { recordEvent } = useAnalytics(); const { dismissWelcomeBanner } = useWelcomeBanner(); const { recommendedModules, submitEvaluation, saveEvaluationResult } = @@ -131,8 +127,7 @@ const WelcomeFlow: FC< Props > = ( { { 'connection' === currentStep && ( ) } { 'evaluation' === currentStep && ( diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index f302d8567e765..49d4563fabc82 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -99,6 +99,7 @@ interface Window { showFullJetpackStatsCard: boolean; videoPressStats: boolean; }; + purchaseToken: string; lifecycleStats: { historicallyActiveModules: JetpackModule[]; brokenModules: { diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php index 0fcc2be3e8412..8af8258e57ddc 100644 --- a/projects/packages/my-jetpack/src/class-initializer.php +++ b/projects/packages/my-jetpack/src/class-initializer.php @@ -201,6 +201,28 @@ public static function can_use_analytics() { return $tracking->should_enable_tracking( new Terms_Of_Service(), $status ); } + + /** + * Gets a purchase token that is used for Jetpack logged out visitor checkout. + * The purchase token should be appended to all CTA url's that lead to checkout. + * + * @return string|boolean + */ + protected static function get_purchase_token() { + if ( ! self::current_user_can_purchase() ) { + return false; + } + + $purchase_token = \Jetpack_Options::get_option( 'purchase_token', false ); + + if ( $purchase_token ) { + return $purchase_token; + } + // If the purchase token is not saved in the options table yet, then add it. + \Jetpack_Options::update_option( 'purchase_token', self::generate_purchase_token(), true ); + return \Jetpack_Options::get_option( 'purchase_token', false ); + } + /** * Enqueue admin page assets. * @@ -265,6 +287,7 @@ public static function enqueue_scripts() { 'loadAddLicenseScreen' => self::is_licensing_ui_enabled(), 'adminUrl' => esc_url( admin_url() ), 'IDCContainerID' => static::get_idc_container_id(), + 'purchaseToken' => self::get_purchase_token(), 'userIsAdmin' => current_user_can( 'manage_options' ), 'userIsNewToJetpack' => self::is_jetpack_user_new(), 'lifecycleStats' => array( @@ -967,4 +990,35 @@ public static function alert_if_missing_connection( array $red_bubble_slugs ) { return $red_bubble_slugs; } + + /** + * Generates a purchase token that is used for Jetpack logged out visitor checkout. + * + * @return string + */ + protected static function generate_purchase_token() { + return wp_generate_password( 12, false ); + } + + /** + * Determine if the current user is allowed to make Jetpack purchases without + * a WordPress.com account + * + * @return boolean True if the user can make purchases, false if not + */ + public static function current_user_can_purchase() { + // The site must be site-connected to Jetpack (no users connected). + $connection_manager = new Connection_Manager(); + + if ( ! $connection_manager->is_site_connection() ) { + return false; + } + + // Make sure only administrators can make purchases. + if ( ! current_user_can( 'manage_options' ) ) { + return false; + } + + return true; + } }