Skip to content

Commit 9e95662

Browse files
authored
Add ExPlat experiment: Payments task onboarding flow skips Connect page (#9383)
1 parent f34c48c commit 9e95662

11 files changed

+136
-57
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Significance: patch
2+
Type: add
3+
Comment: Add ExPlat experiment for Payments task originated onboarding flows.
4+
5+

client/onboarding/index.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,7 @@ const initialData = {
8585

8686
const OnboardingPage: React.FC = () => {
8787
useEffect( () => {
88-
const urlParams = new URLSearchParams( window.location.search );
89-
const source =
90-
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown';
91-
trackStarted( source );
88+
trackStarted();
9289

9390
// Remove loading class and add those required for full screen.
9491
document.body.classList.remove( 'woocommerce-admin-is-loading' );

client/onboarding/step.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@ const Step: React.FC< Props > = ( { name, children, showHeading = true } ) => {
2424
const { trackAbandoned } = useTrackAbandoned();
2525
const { prevStep, exit } = useStepperContext();
2626
const handleExit = () => {
27-
const urlParams = new URLSearchParams( window.location.search );
28-
const source =
29-
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown';
30-
31-
trackAbandoned( 'exit', source );
27+
trackAbandoned( 'exit' );
3228
exit();
3329
};
3430

client/onboarding/steps/embedded-kyc.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const EmbeddedKyc: React.FC< Props > = ( { continueKyc = false } ) => {
132132
setLoading( false );
133133
setLoadErrorMessage(
134134
__(
135-
"Failed to create account session. Please check that you're using the latest version of WooCommerce Payments.",
135+
"Failed to create account session. Please check that you're using the latest version of WooPayments.",
136136
'woocommerce-payments'
137137
)
138138
);

client/onboarding/steps/loading.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,26 +57,26 @@ const LoadingStep: React.FC< Props > = () => {
5757
const handleComplete = async () => {
5858
const { connectUrl } = wcpaySettings;
5959

60-
const urlParams = new URLSearchParams( window.location.search );
61-
const urlSource =
62-
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown';
63-
64-
let isEligible;
60+
let isPoEligible;
6561
try {
66-
isEligible = await isEligibleForPo();
62+
isPoEligible = await isEligibleForPo();
6763
} catch ( error ) {
6864
// fall back to full KYC scenario.
6965
// TODO maybe log these errors in future, e.g. with tracks.
70-
isEligible = false;
66+
isPoEligible = false;
7167
}
7268

73-
trackRedirected( isEligible, urlSource );
69+
trackRedirected( isPoEligible );
7470
removeTrackListener();
7571

72+
const urlParams = new URLSearchParams( window.location.search );
73+
7674
window.location.href = addQueryArgs( connectUrl, {
7775
self_assessment: fromDotNotation( data ),
78-
progressive: isEligible,
79-
source: urlSource,
76+
progressive: isPoEligible,
77+
source:
78+
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) ||
79+
'unknown',
8080
from: 'WCPAY_ONBOARDING_WIZARD',
8181
} );
8282
};

client/onboarding/tracking.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ const stepElapsed = () => {
2323
return result;
2424
};
2525

26-
export const trackStarted = ( source: string ): void => {
26+
export const trackStarted = (): void => {
27+
// Initialize the elapsed time tracking
2728
startTime = stepStartTime = Date.now();
2829

30+
const urlParams = new URLSearchParams( window.location.search );
31+
2932
recordEvent( 'wcpay_onboarding_flow_started', {
30-
source,
33+
source:
34+
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown',
3135
} );
3236
};
3337

@@ -42,14 +46,14 @@ export const trackStepCompleted = ( step: string ): void => {
4246
trackedSteps.add( step );
4347
};
4448

45-
export const trackRedirected = (
46-
isEligible: boolean,
47-
source: string
48-
): void => {
49+
export const trackRedirected = ( isPoEligible: boolean ): void => {
50+
const urlParams = new URLSearchParams( window.location.search );
51+
4952
recordEvent( 'wcpay_onboarding_flow_redirected', {
50-
is_po_eligible: isEligible,
53+
is_po_eligible: isPoEligible,
5154
elapsed: elapsed( startTime ),
52-
source,
55+
source:
56+
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) || 'unknown',
5357
} );
5458
};
5559

@@ -66,13 +70,13 @@ export const trackEligibilityModalClosed = (
6670
} );
6771

6872
export const useTrackAbandoned = (): {
69-
trackAbandoned: ( method: 'hide' | 'exit', source: string ) => void;
73+
trackAbandoned: ( method: 'hide' | 'exit' ) => void;
7074
removeTrackListener: () => void;
7175
} => {
7276
const { errors, touched } = useOnboardingContext();
7377
const { currentStep: step } = useStepperContext();
7478

75-
const trackEvent = ( method = 'hide', source = 'unknown' ) => {
79+
const trackEvent = ( method = 'hide' ) => {
7680
const event =
7781
method === 'hide'
7882
? 'wcpay_onboarding_flow_hidden'
@@ -81,21 +85,21 @@ export const useTrackAbandoned = (): {
8185
( field ) => touched[ field as keyof OnboardingFields ]
8286
);
8387

88+
const urlParams = new URLSearchParams( window.location.search );
89+
8490
recordEvent( event, {
8591
step,
8692
errored,
8793
elapsed: elapsed( startTime ),
88-
source,
94+
source:
95+
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) ||
96+
'unknown',
8997
} );
9098
};
9199

92100
const listener = () => {
93101
if ( document.visibilityState === 'hidden' ) {
94-
const urlParams = new URLSearchParams( window.location.search );
95-
const source =
96-
urlParams.get( 'source' )?.replace( /[^\w-]+/g, '' ) ||
97-
'unknown';
98-
trackEvent( 'hide', source );
102+
trackEvent( 'hide' );
99103
}
100104
};
101105

@@ -108,8 +112,8 @@ export const useTrackAbandoned = (): {
108112
}, [ step, errors, touched ] );
109113

110114
return {
111-
trackAbandoned: ( method: string, source = 'unknown' ) => {
112-
trackEvent( method, source );
115+
trackAbandoned: ( method: string ) => {
116+
trackEvent( method );
113117
document.removeEventListener( 'visibilitychange', listener );
114118
},
115119
removeTrackListener: () =>

client/utils/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,15 @@ export const getExportLanguageOptions = () => {
310310
},
311311
];
312312
};
313+
314+
/**
315+
* Given an object, remove all properties with null or undefined values.
316+
*
317+
* @param {Object} obj The object to remove empty properties from.
318+
* @return {Object|any} A new object with all properties with null or undefined values removed.
319+
*/
320+
export const objectRemoveEmptyProperties = ( obj ) => {
321+
return Object.keys( obj )
322+
.filter( ( k ) => obj[ k ] !== null && obj[ k ] !== undefined )
323+
.reduce( ( a, k ) => ( { ...a, [ k ]: obj[ k ] } ), {} );
324+
};

includes/class-wc-payments-account.php

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -899,8 +899,9 @@ public function maybe_redirect_from_onboarding_wizard_page(): bool {
899899
}
900900

901901
/**
902-
* Redirects connect page (payments/connect) to the overview page for stores that
903-
* have a working Jetpack connection and a valid Stripe account.
902+
* Maybe redirects the connect page (payments/connect)
903+
*
904+
* We redirect to the overview page for stores that have a working Jetpack connection and a valid Stripe account.
904905
*
905906
* Note: Connect _page_ links are not the same as connect links.
906907
* Connect links are used to start/re-start/continue the onboarding flow and they are independent of
@@ -953,6 +954,28 @@ public function maybe_redirect_from_connect_page(): bool {
953954
return true;
954955
}
955956

957+
// Determine from where the merchant was directed to the Connect page.
958+
$from = WC_Payments_Onboarding_Service::get_from();
959+
960+
// If the user came from the core Payments task list item,
961+
// we run an experiment to skip the Connect page
962+
// and go directly to the Jetpack connection flow and/or onboarding wizard.
963+
if ( WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK === $from
964+
&& WC_Payments_Utils::is_in_core_payments_task_onboarding_flow_treatment_mode() ) {
965+
966+
// We use a connect link to allow our logic to determine what comes next:
967+
// the Jetpack connection setup and/or onboarding wizard (MOX).
968+
$this->redirect_service->redirect_to_wcpay_connect(
969+
// The next step should treat the merchant as coming from the Payments task list item,
970+
// not the Connect page.
971+
WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK,
972+
[
973+
'source' => WC_Payments_Onboarding_Service::get_source(),
974+
]
975+
);
976+
return true;
977+
}
978+
956979
return false;
957980
}
958981

@@ -1274,7 +1297,9 @@ public function maybe_handle_onboarding() {
12741297
'WooPayments'
12751298
),
12761299
WC_Payments_Onboarding_Service::FROM_WPCOM_CONNECTION,
1277-
[ 'source' => $onboarding_source ]
1300+
[
1301+
'source' => $onboarding_source,
1302+
]
12781303
);
12791304

12801305
return;
@@ -1315,11 +1340,18 @@ public function maybe_handle_onboarding() {
13151340
$from,
13161341
[
13171342
WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_SETTINGS,
1318-
WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK,
13191343
WC_Payments_Onboarding_Service::FROM_STRIPE,
13201344
],
13211345
true
13221346
)
1347+
/**
1348+
* We are running an experiment to skip the Connect page for Payments Task flows.
1349+
* Only redirect to the Connect page if the user is not in the experiment's treatment mode.
1350+
*
1351+
* @see self::maybe_redirect_from_connect_page()
1352+
*/
1353+
|| ( WC_Payments_Onboarding_Service::FROM_WCADMIN_PAYMENTS_TASK === $from
1354+
&& ! WC_Payments_Utils::is_in_core_payments_task_onboarding_flow_treatment_mode() )
13231355
// This is a weird case, but it is best to handle it.
13241356
|| ( WC_Payments_Onboarding_Service::FROM_ONBOARDING_WIZARD === $from && ! $this->has_working_jetpack_connection() )
13251357
) {
@@ -1376,7 +1408,9 @@ public function maybe_handle_onboarding() {
13761408
/* translators: %s: error message. */
13771409
sprintf( __( 'There was a problem connecting your store to WordPress.com: "%s"', 'woocommerce-payments' ), $e->getMessage() ),
13781410
WC_Payments_Onboarding_Service::FROM_WPCOM_CONNECTION,
1379-
[ 'source' => $onboarding_source ]
1411+
[
1412+
'source' => $onboarding_source,
1413+
]
13801414
);
13811415
return;
13821416
}
@@ -1392,7 +1426,9 @@ public function maybe_handle_onboarding() {
13921426
// When we redirect to the onboarding wizard, we carry over the `from`, if we have it.
13931427
// This is because there is no interim step between the user clicking the connect link and the onboarding wizard.
13941428
! empty( $from ) ? $from : $next_step_from,
1395-
[ 'source' => $onboarding_source ]
1429+
[
1430+
'source' => $onboarding_source,
1431+
]
13961432
);
13971433
return;
13981434
}
@@ -1516,7 +1552,9 @@ public function maybe_handle_onboarding() {
15161552
'WooPayments'
15171553
),
15181554
null,
1519-
[ 'source' => $onboarding_source ]
1555+
[
1556+
'source' => $onboarding_source,
1557+
]
15201558
);
15211559
return;
15221560
}
@@ -1984,16 +2022,16 @@ private function finalize_connection( string $state, string $mode, array $additi
19842022
update_option( '_wcpay_onboarding_stripe_connected', [ 'is_existing_stripe_account' => false ] );
19852023

19862024
// Track account connection finish.
1987-
$incentive_id = ! empty( $_GET['promo'] ) ? sanitize_text_field( wp_unslash( $_GET['promo'] ) ) : '';
1988-
$event_properties = [
2025+
$incentive_id = ! empty( $_GET['promo'] ) ? sanitize_text_field( wp_unslash( $_GET['promo'] ) ) : '';
2026+
$tracks_props = [
19892027
'incentive' => $incentive_id,
19902028
'mode' => 'live' !== $mode ? 'test' : 'live',
19912029
'from' => $additional_args['from'] ?? '',
19922030
'source' => $additional_args['source'] ?? '',
19932031
];
19942032
$this->tracks_event(
19952033
self::TRACKS_EVENT_ACCOUNT_CONNECT_FINISHED,
1996-
$event_properties
2034+
$tracks_props
19972035
);
19982036

19992037
$params = $additional_args;

includes/class-wc-payments-redirect-service.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,23 @@ public function redirect_to( string $location ): void {
4949
}
5050

5151
/**
52-
* Redirects to the wcpay-connect URL, which then redirects to the KYC flow.
52+
* Redirects to a wcpay-connect URL which then handles the next step for the onboarding flow.
5353
*
54-
* This URL is used by the KYC reminder email. We can't take the merchant
55-
* directly to the wcpay-connect URL because it's nonced, and the
56-
* nonce will likely be expired by the time the user follows the link.
57-
* That's why we need this middleman instead.
54+
* This is a sure way to ensure that the user is redirected to the correct URL to continue their onboarding.
5855
*
59-
* @param string $from Source of the redirect.
56+
* @param string $from Source of the redirect.
57+
* @param array $additional_params Optional. Additional URL params to add to the redirect URL.
6058
*/
61-
public function redirect_to_wcpay_connect( string $from = '' ): void {
59+
public function redirect_to_wcpay_connect( string $from = '', array $additional_params = [] ): void {
6260
// Take the user to the 'wcpay-connect' URL.
6361
// We handle creating and redirecting to the account link there.
6462
$params = [
6563
'wcpay-connect' => '1',
6664
'_wpnonce' => wp_create_nonce( 'wcpay-connect' ),
6765
];
66+
67+
$params = array_merge( $params, $additional_params );
68+
6869
if ( '' !== $from ) {
6970
$params['from'] = $from;
7071
}

includes/class-wc-payments-utils.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,25 @@ public static function get_last_refund_from_order_id( $order_id ) {
910910
return null;
911911
}
912912

913+
/**
914+
* Check to see if the current user is in Core Payments task onboarding flow experiment treatment mode.
915+
*
916+
* @return bool
917+
*/
918+
public static function is_in_core_payments_task_onboarding_flow_treatment_mode(): bool {
919+
if ( ! isset( $_COOKIE['tk_ai'] ) ) {
920+
return false;
921+
}
922+
923+
$abtest = new \WCPay\Experimental_Abtest(
924+
sanitize_text_field( wp_unslash( $_COOKIE['tk_ai'] ) ),
925+
'woocommerce',
926+
'yes' === get_option( 'woocommerce_allow_tracking', 'no' )
927+
);
928+
929+
return 'treatment' === $abtest->get_variation( 'woopayments_core_payments_task_onboarding_flow_2024_v1' );
930+
}
931+
913932
/**
914933
* Helper function to check whether to show default new onboarding flow or as an exception disable it (if specific constant is set) .
915934
*

0 commit comments

Comments
 (0)