Automattic\WooCommerce\Internal\Admin\Settings\PaymentsProviders\WooPayments

WooPaymentsService::disable_test_accountpublicWC 1.0

Disable a test account during the switch-to-live onboarding flow.

Method of the class: WooPaymentsService{}

No Hooks.

Returns

Array. The response from the WooPayments API.

Usage

$WooPaymentsService = new WooPaymentsService();
$WooPaymentsService->disable_test_account( $location, $from, ?string $source ): array;
$location(string) (required)
The location for which we are onboarding. This is an ISO 3166-1 alpha-2 country code.
$from(string)
Where in the UI the request is coming from. If not provided, it will identify the origin as the WC Admin Payments settings.
Default: ''
?string $source
.
Default: self::SESSION_ENTRY_DEFAULT

WooPaymentsService::disable_test_account() code WC 11.0.0

public function disable_test_account( string $location, string $from = '', ?string $source = self::SESSION_ENTRY_DEFAULT ): array {
	$this->check_if_onboarding_action_is_acceptable();

	// Ensure the payment gateways logic is initialized in case actions need to be taken on payment gateway changes.
	WC()->payment_gateways();

	$response = array(
		'success' => true,
	);

	$event_props = array();
	$source      = $this->validate_onboarding_source( $source );

	$endpoint = '';

	// Both internal calls take the same parameters; only the endpoint differs per account type.
	$params = array(
		'from'   => ! empty( $from ) ? esc_attr( $from ) : self::FROM_PAYMENT_SETTINGS,
		'source' => $source,
	);

	// The same message is reused wherever an unexpected exception is converted to a WP_Error.
	$exception_error_message = esc_html__( 'An unexpected error happened while disabling the test account.', 'woocommerce' );

	// Briefly lock the onboarding while Core determines the account transition to perform.
	// The internal WooPayments endpoint must run after this lock is cleared because it may
	// trigger account deletion webhooks that also touch the shared NOX lock option.
	$this->set_onboarding_lock();

	try {
		$had_test_account    = $this->has_test_account();
		$had_sandbox_account = $this->has_sandbox_account();

		$event_props = array(
			'account_type' => $had_test_account ? 'test_drive' : ( $had_sandbox_account ? 'sandbox' : 'unknown' ),
			'source'       => $source,
		);

		if ( $had_test_account ) {
			// Prepare the WooPayments API disable call for Phase 2, after the lock is released.
			$endpoint = '/wc/v3/payments/onboarding/test_drive_account/disable';
		} elseif ( $had_sandbox_account ) {
			// Prepare the WooPayments API onboarding reset call for Phase 2, after the lock is released.
			$endpoint = '/wc/v3/payments/onboarding/reset';
		}
	} catch ( Exception $e ) {
		// Convert the exception to a WP_Error; the onboarding lock is released in the finally below.
		$response = $this->get_onboarding_client_api_exception_error( $e, $exception_error_message );
	} finally {
		// Unlock before making the internal WooPayments request to avoid self-conflicting
		// with WooPayments account cleanup and account.deleted webhook side effects.
		$this->clear_onboarding_lock();
	}

	// Phase 2 runs after the shared lock is released to avoid the account.deleted webhook
	// self-conflict. The WooPayments endpoint is not idempotent: a duplicate concurrent call is
	// guarded against re-deleting the account (WooPayments overwrites its account cache before
	// the delete, so is_stripe_connected() short-circuits), but it still surfaces to the second
	// caller as a hard ApiException( FAILED_DEPENDENCY ) via the is_wp_error() check below.
	// Request-scoped locking for that residual window is deferred to the broader
	// Core/WooPayments shared-lock contract.
	if ( ! is_wp_error( $response ) && ! empty( $endpoint ) ) {
		try {
			$response = $this->proxy->call_static(
				Utils::class,
				'rest_endpoint_post_request',
				$endpoint,
				$params
			);
		} catch ( Exception $e ) {
			// Convert the exception to a WP_Error so the failure is surfaced to the caller.
			$response = $this->get_onboarding_client_api_exception_error( $e, $exception_error_message );
		}
	}

	// Make sure the onboarding mode is reset.
	if ( class_exists( 'WC_Payments_Onboarding_Service' ) && defined( 'WC_Payments_Onboarding_Service::TEST_MODE_OPTION' ) ) {
		$this->proxy->call_function( 'update_option', Constants::get_constant( 'WC_Payments_Onboarding_Service::TEST_MODE_OPTION' ), 'no' );
	}

	// Track the failure to disable the test account.
	if ( is_wp_error( $response ) || ! is_array( $response ) || empty( $response['success'] ) ) {
		$this->record_event(
			self::EVENT_PREFIX . 'onboarding_test_account_disable_error',
			$location,
			array(
				'source' => $source,
			)
		);
	}

	if ( is_wp_error( $response ) ) {
		throw new ApiException(
			'woocommerce_woopayments_onboarding_client_api_error',
			esc_html( $response->get_error_message() ),
			(int) WP_Http::FAILED_DEPENDENCY,
			map_deep( (array) $response->get_error_data(), 'esc_html' )
		);
	}

	if ( ! is_array( $response ) || empty( $response['success'] ) ) {
		throw new ApiException(
			'woocommerce_woopayments_onboarding_client_api_error',
			esc_html__( 'Failed to disable the test account.', 'woocommerce' ),
			(int) WP_Http::FAILED_DEPENDENCY
		);
	}

	// The account mutation above has already committed, so the following is internal NOX state
	// sync, not a new user action. Use the internal record path that does not re-check the
	// onboarding lock: Phase 2 ran unlocked, so a concurrent request may now hold the lock, and
	// re-checking it here would turn an already-successful disable into an onboarding-locked
	// error (the shared, token-less lock also can't be safely reacquired around this bookkeeping).
	// See record_onboarding_step_completed().

	// For sanity, make sure the payment methods step is marked as completed.
	// This is to avoid the user being prompted to set up payment methods again.
	$this->record_onboarding_step_completed( self::ONBOARDING_STEP_PAYMENT_METHODS, $location );
	// For sanity, make sure the test account step is marked as completed and not blocked or failed.
	// After disabling a test account, the user should be prompted to set up a live account.
	$this->record_onboarding_step_completed( self::ONBOARDING_STEP_TEST_ACCOUNT, $location );
	$this->clear_onboarding_step_blocked( self::ONBOARDING_STEP_TEST_ACCOUNT, $location );
	$this->clear_onboarding_step_failed( self::ONBOARDING_STEP_TEST_ACCOUNT, $location );
	// Clear the NOX profile data for the business verification step sub-step data.
	// This way the user will be prompted to complete ALL the business verification sub-steps.
	$business_verification_sub_step_data = $this->get_nox_profile_onboarding_step_data_entry( self::ONBOARDING_STEP_BUSINESS_VERIFICATION, $location, 'sub_steps', array() );
	if ( ! empty( $business_verification_sub_step_data ) ) {
		$this->save_nox_profile_onboarding_step_data_entry( self::ONBOARDING_STEP_BUSINESS_VERIFICATION, $location, 'sub_steps', array() );
	}

	// Record an event for the test account being disabled.
	$this->record_event(
		self::EVENT_PREFIX . 'onboarding_test_account_disabled',
		$location,
		$event_props
	);

	return $response;
}