Support "create account" option in checkout block (https://github.com/woocommerce/woocommerce-blocks/pull/2851)
* prototype 'create account' checkbox in checkout block * expose store config for generating password/username to blocks: + use FILTER_VALIDATE_BOOLEAN instead of hard-coded `yes` * stub out signup form in checkout block * context / provider to store checkout signup form data * revert signup form - checkout block will always generate username etc * persist signup checkbox in checkout state & pass to checkout API * add `create_account` param to order API, fix name in client POST * handle creating user account as part of order (first cut) * ensure the order is associated with the new customer * only show 'create account' checkbox when appropriate (guest checkout) * remove unnecessary username/password variables * refactor account-creation logic into functions: - clarify inputs and outputs - use RouteException for error handling - use woo options directly, avoid dependency on WC_Checkout * update "email exists" error message to use existing error message text * handle all known errors from wc_create_new_customer + use core message * only show "create account" checkbox to shopper when necessary: - if guest checkout is disabled, user must create account - not optional * only show "create account" if account creation is optional: - fixes incorrect logic in previous commit - add some comments to clarify * fix create account logic in API when checkout requires account: - use correct woo setting option name - reverse logic to match option = allow guest false means registration required * strip html tags from create account error messages * temporarily force enable autogenerate user/pass in new account API * fix rebase errors * add new allowCreateAccount attribute in checkout block * show/hide `Create account` checkbox dependent on block attribute: - previously was dependent on store setting * new create user API, with set initial password email (first cut): - use core register_new_user for creating the user - this triggers core "set new password" email - generate username using logic lifted from WC core - rough cut, lots to tidy/polish here * remove alternative/unused create account function * set `Customer` role for signups during checkout * eslint fix - switch case break * remove comments that mirror code & might go stale * tidy func comment * remove unused function * use store setting `allow signup` for default value of new block option * refactor order signup logic to service class first cut: - new CreateAccount service - hook up via custom action (for now at least) - paste over existing create account logic (temporary - will be replaced) * adapt wc_create_new_customer logic in CreateAccount service (WIP) * set default_password_nag on new account + throw instead of WP_Error * rename `createAccount` => `shouldCreateAccount` to clarify meaning * fix checkout block - renamed `shouldCreateAccount` (missed in prev commit) * prototype sending alternative email template for checkout signup * add magic link to set password to blocks new account html email * tidy up new account email templates - set password link, subject/heading * use same id so merchant setting tweaks apply to our new improved email * remove logging * code tidies in CreateAccount service: - remove unnecessary constructor - type-hint in should_create_customer_account - streamline logic in should_create_customer_account - remove unnecessary `empty` check - add comments to illuminate different use-cases handled by should_create_customer_account * don't provide password to new account email templates (no longer used) * declare dependencies in root namespace * code tidies on new account email class: - correct namespace and camelcase name - declare class in file, don't instantiate; instantiate in client code (CreateAccount service) when used - no require/file import, use `use` * move CustomerNewEmail to folder matching namespace * use Package->get_path for email template paths: - CreateAccount service now depends on Package - CreateAccount passes Package to email class so it can use `get_path` - note: CustomerNewAccount is not registered with DI container as it needs to be instantiated after Woo init (for `WC_Email`) - shift email templates to {plugin}/templates, consistent with WP convention * call CreateAccount::from_order_request directly, no custom hook: - custom hook is not appropriate as we may not want to allow extensibility in this way - TBD * add appropriate margin above create account checkbox * remove unnecessary direct-access protection * generalise name of error-handling method * simplify CustomerNewAccount - instantiate directly, when needed * remove unused new_account_email member - now instantiated on demand * numerous fixes and updates due to rebase changes * fix typo in name of CustomerNewAccount php file (missing `n`) * experiment - link to lost-password form in my-account (prototype branded screen) * Revert "experiment - link to lost-password form in my-account (prototype branded screen)" This reverts commit e1dc6dd5e9f0218ede81da92188d813c2d0856d9. * feature gate CreateAccount service init to dev build only + + remove stale comment * feature gate front end "Create account" checkbox to feature plugin only * feature gate editor "allow signup" option to dev build only * feature gate checkout api create account - dev build only * tweak feature gating PHP logic so it's robust: - all PHP feature gating is in the service class - all publicly-available methods return early if feature gate off - Checkout rest API transparently calls service - no explicit feature gate at API level * ensure frontend/editor features are feature gated (isExperimentalBuild is a function) * feature gate value of checkoutAllowsSignup - can only be true in feature plugin * fix a / an typo in comment Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com> * remove commented code * hello world unit test (doesn't test anything yet) * add a command for running unit tests when container already up: - this should probably move to another PR/branch * basic tests of core logic in CreateAccount service * import isExperimentalBuild direct: - import from alias package was causing an issue, likely a dependency cycle * refactor from_order_request to return new user ID so it's easier to test * test creating a customer from an order + rest request: - i.e. a full end-to-end integration test * delete redundant test and tidy comments * generalise test to provider format * refactor create-dup-user err test to use same approach as success test * add test for when user should not be created * don't hard-code options in "create" test, remove redundant provider in no-account-requested test * de-generalise "user already signed up" test * add test for malformed email * flesh out & comment successful signup tests * flesh out "invalid email" tests * clarify no account requested test comment * remove phpunit:quick - I don't think it's needed * add comment explaining this is an integration test * experiment – disable feature flag, is this why the tests are failing? * revert test commit - restore feature gate (experimental flag) * skip all tests if CreateAccount is disabled due to feature flag * d'oh - expose CreateAccount:is_feature_enabled so can be used in tests * add jsdoc for checkout-state shouldCreateAccount field * remove unnecessary comment + fix whitespace/indentation * simulate logged-out user for createaccount signup tests * use a single, compound if statement for early return (review nitpick) * don't hide `checkoutAllowsSignup` store setting behind feature flag: - the feature flag should be used to enable/disable behaviour - it's dangerous to adjust store settings/options based on feature flag * rejig tests so they require woocommerce_blocks_phase==3: - make feature gate method private to avoid exposing - remove feature flag check & test skip for other builds - set blocks phase in travis config * remove redundant user-logout in test setup - cleaner to just require this * use WP function bracket style (same line) Co-authored-by: Darren Ethier <darren@roughsmootheng.in> Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
This commit is contained in:
parent
4102594de0
commit
f000fb4f7a
|
@ -76,6 +76,7 @@ jobs:
|
|||
- WOOCOMMERCE_BLOCKS_PHASE=3
|
||||
- PHP_UNIT=1
|
||||
script:
|
||||
- echo 'woocommerce_blocks_phase = 3' > blocks.ini
|
||||
- npm run phpunit
|
||||
- name: PHP 5.6/unit-tests/Latest WP
|
||||
php: 5.6
|
||||
|
@ -84,6 +85,7 @@ jobs:
|
|||
- WOOCOMMERCE_BLOCKS_PHASE=3
|
||||
- PHP_UNIT=1
|
||||
script:
|
||||
- echo 'woocommerce_blocks_phase = 3' > blocks.ini
|
||||
- npm run phpunit
|
||||
- name: PHP Linting Check
|
||||
php: 7.3
|
||||
|
|
|
@ -17,6 +17,7 @@ const {
|
|||
INCREMENT_CALCULATING,
|
||||
DECREMENT_CALCULATING,
|
||||
SET_ORDER_ID,
|
||||
SET_SHOULD_CREATE_ACCOUNT,
|
||||
SET_ORDER_NOTES,
|
||||
} = TYPES;
|
||||
|
||||
|
@ -65,6 +66,10 @@ export const actions = {
|
|||
type: SET_ORDER_ID,
|
||||
orderId,
|
||||
} ),
|
||||
setShouldCreateAccount: ( shouldCreateAccount ) => ( {
|
||||
type: SET_SHOULD_CREATE_ACCOUNT,
|
||||
shouldCreateAccount,
|
||||
} ),
|
||||
setOrderNotes: ( orderNotes ) => ( {
|
||||
type: SET_ORDER_NOTES,
|
||||
orderNotes,
|
||||
|
|
|
@ -28,6 +28,7 @@ export const DEFAULT_STATE = {
|
|||
orderId: checkoutData.order_id,
|
||||
orderNotes: '',
|
||||
customerId: checkoutData.customer_id,
|
||||
shouldCreateAccount: false,
|
||||
processingResponse: null,
|
||||
};
|
||||
|
||||
|
|
|
@ -335,6 +335,9 @@ export const CheckoutStateProvider = ( {
|
|||
hasOrder: !! checkoutState.orderId,
|
||||
customerId: checkoutState.customerId,
|
||||
orderNotes: checkoutState.orderNotes,
|
||||
shouldCreateAccount: checkoutState.shouldCreateAccount,
|
||||
setShouldCreateAccount: ( value ) =>
|
||||
dispatch( actions.setShouldCreateAccount( value ) ),
|
||||
};
|
||||
return (
|
||||
<CheckoutContext.Provider value={ checkoutData }>
|
||||
|
|
|
@ -18,6 +18,7 @@ const {
|
|||
DECREMENT_CALCULATING,
|
||||
SET_ORDER_ID,
|
||||
SET_ORDER_NOTES,
|
||||
SET_SHOULD_CREATE_ACCOUNT,
|
||||
} = TYPES;
|
||||
|
||||
const {
|
||||
|
@ -68,11 +69,12 @@ export const prepareResponseData = ( data ) => {
|
|||
* @param {string} action.type Type of action.
|
||||
* @param {string} action.orderId Order ID.
|
||||
* @param {Array} action.orderNotes Order notes.
|
||||
* @param {boolean} action.shouldCreateAccount True if shopper has requested a user account (signup checkbox).
|
||||
* @param {Object} action.data Other action payload.
|
||||
*/
|
||||
export const reducer = (
|
||||
state = DEFAULT_STATE,
|
||||
{ url, type, orderId, orderNotes, data }
|
||||
{ url, type, orderId, orderNotes, shouldCreateAccount, data }
|
||||
) => {
|
||||
let newState = state;
|
||||
switch ( type ) {
|
||||
|
@ -190,6 +192,14 @@ export const reducer = (
|
|||
orderId,
|
||||
};
|
||||
break;
|
||||
case SET_SHOULD_CREATE_ACCOUNT:
|
||||
if ( shouldCreateAccount !== state.shouldCreateAccount ) {
|
||||
newState = {
|
||||
...state,
|
||||
shouldCreateAccount,
|
||||
};
|
||||
}
|
||||
break;
|
||||
case SET_ORDER_NOTES:
|
||||
if ( state.orderNotes !== orderNotes ) {
|
||||
newState = {
|
||||
|
|
|
@ -63,6 +63,7 @@ const CheckoutProcessor = () => {
|
|||
isBeforeProcessing: checkoutIsBeforeProcessing,
|
||||
isComplete: checkoutIsComplete,
|
||||
orderNotes,
|
||||
shouldCreateAccount,
|
||||
} = useCheckoutContext();
|
||||
const { hasValidationErrors } = useValidationContext();
|
||||
const { shippingAddress, shippingErrorStatus } = useShippingDataContext();
|
||||
|
@ -188,6 +189,7 @@ const CheckoutProcessor = () => {
|
|||
billing_address: currentBillingData.current,
|
||||
shipping_address: currentShippingAddress.current,
|
||||
customer_note: orderNotes,
|
||||
should_create_account: shouldCreateAccount,
|
||||
};
|
||||
if ( cartNeedsPayment ) {
|
||||
data = {
|
||||
|
@ -258,6 +260,7 @@ const CheckoutProcessor = () => {
|
|||
receiveCart,
|
||||
dispatchActions,
|
||||
orderNotes,
|
||||
shouldCreateAccount,
|
||||
] );
|
||||
// redirect when checkout is complete and there is a redirect url.
|
||||
useEffect( () => {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { HAS_DARK_EDITOR_STYLE_SUPPORT } from '@woocommerce/block-settings';
|
||||
import {
|
||||
HAS_DARK_EDITOR_STYLE_SUPPORT,
|
||||
CHECKOUT_ALLOWS_SIGNUP,
|
||||
} from '@woocommerce/block-settings';
|
||||
|
||||
const blockAttributes = {
|
||||
isPreview: {
|
||||
|
@ -17,6 +20,10 @@ const blockAttributes = {
|
|||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
allowCreateAccount: {
|
||||
type: 'boolean',
|
||||
default: CHECKOUT_ALLOWS_SIGNUP,
|
||||
},
|
||||
showApartmentField: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
|
|
|
@ -92,7 +92,12 @@ const Checkout = ( { attributes, scrollToTop } ) => {
|
|||
return <CheckoutOrderError />;
|
||||
}
|
||||
|
||||
if ( ! isEditor && ! customerId && ! CHECKOUT_ALLOWS_GUEST ) {
|
||||
if (
|
||||
! isEditor &&
|
||||
! customerId &&
|
||||
! CHECKOUT_ALLOWS_GUEST &&
|
||||
! attributes.allowCreateAccount
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
{ __(
|
||||
|
@ -108,7 +113,6 @@ const Checkout = ( { attributes, scrollToTop } ) => {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const checkoutClassName = classnames( 'wc-block-checkout', {
|
||||
'has-dark-controls': attributes.hasDarkControls,
|
||||
} );
|
||||
|
@ -124,6 +128,7 @@ const Checkout = ( { attributes, scrollToTop } ) => {
|
|||
showPhoneField={ attributes.showPhoneField }
|
||||
requireCompanyField={ attributes.requireCompanyField }
|
||||
requirePhoneField={ attributes.requirePhoneField }
|
||||
allowCreateAccount={ attributes.allowCreateAccount }
|
||||
/>
|
||||
<div className="wc-block-checkout__actions">
|
||||
{ attributes.showReturnToCart && (
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
PRIVACY_URL,
|
||||
TERMS_URL,
|
||||
CHECKOUT_PAGE_ID,
|
||||
isExperimentalBuild,
|
||||
} from '@woocommerce/block-settings';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { createInterpolateElement } from 'wordpress-element';
|
||||
|
@ -45,6 +46,7 @@ const BlockSettings = ( { attributes, setAttributes } ) => {
|
|||
showPhoneField,
|
||||
requireCompanyField,
|
||||
requirePhoneField,
|
||||
allowCreateAccount,
|
||||
showOrderNotes,
|
||||
showPolicyLinks,
|
||||
showReturnToCart,
|
||||
|
@ -154,6 +156,27 @@ const BlockSettings = ( { attributes, setAttributes } ) => {
|
|||
/>
|
||||
) }
|
||||
</PanelBody>
|
||||
{ isExperimentalBuild() && (
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Account options',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Allow shopper to sign up for a user account during checkout',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ allowCreateAccount }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
allowCreateAccount: ! allowCreateAccount,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
) }
|
||||
<PanelBody
|
||||
title={ __( 'Order notes', 'woo-gutenberg-products-block' ) }
|
||||
>
|
||||
|
|
|
@ -20,6 +20,7 @@ const AddressStep = ( {
|
|||
showApartmentField,
|
||||
showCompanyField,
|
||||
showPhoneField,
|
||||
allowCreateAccount,
|
||||
} ) => {
|
||||
const {
|
||||
defaultAddressFields,
|
||||
|
@ -58,6 +59,7 @@ const AddressStep = ( {
|
|||
<ContactFieldsStep
|
||||
emailValue={ billingFields.email }
|
||||
onChangeEmail={ setEmail }
|
||||
allowCreateAccount={ allowCreateAccount }
|
||||
/>
|
||||
{ needsShipping && (
|
||||
<ShippingFieldsStep
|
||||
|
|
|
@ -5,15 +5,43 @@ import { __ } from '@wordpress/i18n';
|
|||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import { DebouncedValidatedTextInput } from '@woocommerce/base-components/text-input';
|
||||
import { useCheckoutContext } from '@woocommerce/base-context';
|
||||
import {
|
||||
CHECKOUT_ALLOWS_GUEST,
|
||||
isExperimentalBuild,
|
||||
} from '@woocommerce/block-settings';
|
||||
import CheckboxControl from '@woocommerce/base-components/checkbox-control';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import LoginPrompt from './login-prompt';
|
||||
const ContactFieldsStep = ( {
|
||||
emailValue,
|
||||
onChangeEmail,
|
||||
allowCreateAccount,
|
||||
} ) => {
|
||||
const {
|
||||
isProcessing: checkoutIsProcessing,
|
||||
customerId,
|
||||
shouldCreateAccount,
|
||||
setShouldCreateAccount,
|
||||
} = useCheckoutContext();
|
||||
|
||||
const ContactFieldsStep = ( { emailValue, onChangeEmail } ) => {
|
||||
const { isProcessing: checkoutIsProcessing } = useCheckoutContext();
|
||||
|
||||
// "Create Account" checkbox is gated to dev builds only.
|
||||
const createAccountUI = ! customerId &&
|
||||
allowCreateAccount &&
|
||||
CHECKOUT_ALLOWS_GUEST &&
|
||||
isExperimentalBuild() && (
|
||||
<CheckboxControl
|
||||
className="wc-block-checkout__create-account"
|
||||
label={ __(
|
||||
'Create an account?',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
checked={ shouldCreateAccount }
|
||||
onChange={ ( value ) => setShouldCreateAccount( value ) }
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<FormStep
|
||||
id="contact-fields"
|
||||
|
@ -38,6 +66,7 @@ const ContactFieldsStep = ( { emailValue, onChangeEmail } ) => {
|
|||
onChange={ onChangeEmail }
|
||||
required={ true }
|
||||
/>
|
||||
{ createAccountUI }
|
||||
</FormStep>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ const CheckoutForm = ( {
|
|||
showCompanyField,
|
||||
showOrderNotes,
|
||||
showPhoneField,
|
||||
allowCreateAccount,
|
||||
} ) => {
|
||||
const { onSubmit } = useCheckoutContext();
|
||||
|
||||
|
@ -32,6 +33,7 @@ const CheckoutForm = ( {
|
|||
showApartmentField={ showApartmentField }
|
||||
showCompanyField={ showCompanyField }
|
||||
showPhoneField={ showPhoneField }
|
||||
allowCreateAccount={ allowCreateAccount }
|
||||
/>
|
||||
<ShippingOptionsStep />
|
||||
<PaymentMethodStep />
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.wc-block-checkout__create-account,
|
||||
.wc-block-checkout__use-address-for-billing {
|
||||
margin-top: em($gap-large);
|
||||
}
|
||||
|
|
|
@ -177,8 +177,8 @@ class Assets {
|
|||
'privacy' => self::format_page_resource( $page_ids['privacy'] ),
|
||||
'terms' => self::format_page_resource( $page_ids['terms'] ),
|
||||
],
|
||||
'checkoutAllowsGuest' => 'yes' === get_option( 'woocommerce_enable_guest_checkout' ),
|
||||
'checkoutAllowsSignup' => 'yes' === get_option( 'woocommerce_enable_signup_and_login_from_checkout' ),
|
||||
'checkoutAllowsGuest' => filter_var( get_option( 'woocommerce_enable_guest_checkout' ), FILTER_VALIDATE_BOOLEAN ),
|
||||
'checkoutAllowsSignup' => filter_var( get_option( 'woocommerce_enable_signup_and_login_from_checkout' ), FILTER_VALIDATE_BOOLEAN ),
|
||||
'baseLocation' => wc_get_base_location(),
|
||||
'woocommerceBlocksPhase' => WOOCOMMERCE_BLOCKS_PHASE,
|
||||
'hasDarkEditorStyleSupport' => current_theme_supports( 'dark-editor-style' ),
|
||||
|
|
|
@ -17,6 +17,8 @@ use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
|
|||
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\DraftOrders;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount;
|
||||
|
||||
/**
|
||||
* Takes care of bootstrapping the plugin.
|
||||
|
@ -75,6 +77,7 @@ class Bootstrap {
|
|||
BlockAssets::init();
|
||||
}
|
||||
$this->container->get( DraftOrders::class )->init();
|
||||
$this->container->get( CreateAccount::class )->init();
|
||||
$this->container->get( PaymentsApi::class );
|
||||
$this->container->get( RestApi::class );
|
||||
Library::init();
|
||||
|
@ -193,6 +196,12 @@ class Bootstrap {
|
|||
return new DraftOrders( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CreateAccount::class,
|
||||
function( Container $container ) {
|
||||
return new CreateAccount( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use \WP_REST_Request;
|
||||
use \WC_Order;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount;
|
||||
|
||||
/**
|
||||
* Service class implementing new create account behaviour for order processing.
|
||||
*/
|
||||
class CreateAccount {
|
||||
/**
|
||||
* Reference to the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Package $package An instance of (Woo Blocks) Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single method for feature gating logic. Used to gate all non-private methods.
|
||||
*
|
||||
* @return True if Checkout sign-up feature should be made available.
|
||||
*/
|
||||
private static function is_feature_enabled() {
|
||||
// This new checkout signup flow is gated to dev builds for now.
|
||||
// The main reason for this is that we are waiting on an new
|
||||
// set-password endpoint/form in WooCommerce Core.
|
||||
// When that's available we can review this and include in feature
|
||||
// plugin alongside checkout block.
|
||||
return Package::is_experimental_build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init - register handlers for WooCommerce core email hooks.
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! self::is_feature_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Override core email handlers to add our new improved "new account" email.
|
||||
add_action(
|
||||
'woocommerce_email',
|
||||
function ( $wc_emails_instance ) {
|
||||
// Remove core "new account" handler; we are going to replace it.
|
||||
remove_action( 'woocommerce_created_customer_notification', array( $wc_emails_instance, 'customer_new_account' ), 10, 3 );
|
||||
|
||||
// Add custom "new account" handler.
|
||||
add_action(
|
||||
'woocommerce_created_customer_notification',
|
||||
function( $customer_id, $new_customer_data = array(), $password_generated = false ) use ( $wc_emails_instance ) {
|
||||
// If this is a block-based signup, send a new email
|
||||
// with password reset link (no password in email).
|
||||
if ( isset( $new_customer_data['is_checkout_block_customer_signup'] ) ) {
|
||||
$this->customer_new_account( $customer_id, $new_customer_data );
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, trigger the existing legacy email (with new password inline).
|
||||
$wc_emails_instance->customer_new_account( $customer_id, $new_customer_data, $password_generated );
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger new account email.
|
||||
* This is intended as a replacement to WC_Emails::customer_new_account(),
|
||||
* with a set password link instead of emailing the new password in email
|
||||
* content.
|
||||
*
|
||||
* @param int $customer_id The ID of the new customer account.
|
||||
* @param array $new_customer_data Assoc array of data for the new account.
|
||||
*/
|
||||
public function customer_new_account( $customer_id = 0, array $new_customer_data = array() ) {
|
||||
if ( ! self::is_feature_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $customer_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$new_account_email = new CustomerNewAccount( $this->package );
|
||||
$new_account_email->trigger( $customer_id, $new_customer_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user account for specified order and request (if necessary).
|
||||
* If a new account is created:
|
||||
* - The order is associated with the account.
|
||||
* - The user is logged in.
|
||||
*
|
||||
* @param \WC_Order $order The order currently being processed.
|
||||
* @param \WP_REST_Request $request The current request object being handled.
|
||||
*
|
||||
* @throws Exception On error.
|
||||
* @return int The new user id, or 0 if no user was created.
|
||||
*/
|
||||
public function from_order_request( \WC_Order $order, \WP_REST_Request $request ) {
|
||||
if ( ! self::is_feature_enabled() || ! $this->should_create_customer_account( $request ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$customer_id = $this->create_customer_account(
|
||||
$order->get_billing_email(),
|
||||
$order->get_billing_first_name(),
|
||||
$order->get_billing_last_name()
|
||||
);
|
||||
|
||||
// Log the customer in and associate with the order.
|
||||
wc_set_customer_auth_cookie( $customer_id );
|
||||
$order->set_customer_id( get_current_user_id() );
|
||||
|
||||
return $customer_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check request options and store (shop) config to determine if a user account
|
||||
* should be created as part of order processing.
|
||||
*
|
||||
* @param \WP_REST_Request $request The current request object being handled.
|
||||
*
|
||||
* @return boolean True if a new user account should be created.
|
||||
*/
|
||||
protected function should_create_customer_account( \WP_REST_Request $request ) {
|
||||
if ( is_user_logged_in() ) {
|
||||
// User is already logged in - no need to create an account.
|
||||
return false;
|
||||
}
|
||||
|
||||
// From here we know that the shopper is not logged in.
|
||||
|
||||
if ( false === filter_var( get_option( 'woocommerce_enable_guest_checkout' ), FILTER_VALIDATE_BOOLEAN ) ) {
|
||||
// Store requires an account for all checkouts (purchases).
|
||||
// Create an account independent of shopper option in $request.
|
||||
// Note - checkbox is not displayed to shopper in this case.
|
||||
return true;
|
||||
}
|
||||
|
||||
// From here we know that the store allows guest checkout;
|
||||
// shopper can choose whether they sign up (`should_create_account`).
|
||||
|
||||
if ( true === filter_var( $request['should_create_account'], FILTER_VALIDATE_BOOLEAN ) ) {
|
||||
// User has requested an account as part of checkout processing.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an account creation error to an exception.
|
||||
*
|
||||
* @param \WP_Error $error An error object.
|
||||
*
|
||||
* @return Exception.
|
||||
*/
|
||||
private function map_create_account_error( \WP_Error $error ) {
|
||||
switch ( $error->get_error_code() ) {
|
||||
// WordPress core error codes.
|
||||
case 'empty_username':
|
||||
case 'invalid_username':
|
||||
case 'empty_email':
|
||||
case 'invalid_email':
|
||||
case 'email_exists':
|
||||
case 'registerfail':
|
||||
return new \Exception( 'woocommerce_rest_checkout_create_account_failure' );
|
||||
}
|
||||
|
||||
return new \Exception( 'woocommerce_rest_checkout_create_account_failure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new account for a customer (using a new blocks-specific PHP API).
|
||||
*
|
||||
* The account is created with a generated username. The customer is sent
|
||||
* an email notifying them about the account and containing a link to set
|
||||
* their (initial) password.
|
||||
*
|
||||
* Intended as a replacement for wc_create_new_customer in WC core.
|
||||
*
|
||||
* @throws \Exception If an error is encountered when creating the user account.
|
||||
*
|
||||
* @param string $user_email The email address to use for the new account.
|
||||
* @param string $first_name The first name to use for the new account.
|
||||
* @param string $last_name The last name to use for the new account.
|
||||
*
|
||||
* @return int User id if successful
|
||||
*/
|
||||
private function create_customer_account( $user_email, $first_name, $last_name ) {
|
||||
if ( empty( $user_email ) || ! is_email( $user_email ) ) {
|
||||
throw new \Exception( 'registration-error-invalid-email' );
|
||||
}
|
||||
|
||||
if ( email_exists( $user_email ) ) {
|
||||
throw new \Exception( 'registration-error-email-exists' );
|
||||
}
|
||||
|
||||
$username = wc_create_new_customer_username( $user_email );
|
||||
|
||||
// Handle password creation.
|
||||
$password = wp_generate_password();
|
||||
$password_generated = true;
|
||||
|
||||
// Use WP_Error to handle registration errors.
|
||||
$errors = new \WP_Error();
|
||||
|
||||
do_action( 'woocommerce_register_post', $username, $user_email, $errors );
|
||||
|
||||
$errors = apply_filters( 'woocommerce_registration_errors', $errors, $username, $user_email );
|
||||
|
||||
if ( $errors->get_error_code() ) {
|
||||
throw new \Exception( $errors->get_error_code() );
|
||||
}
|
||||
|
||||
$new_customer_data = apply_filters(
|
||||
'woocommerce_new_customer_data',
|
||||
array(
|
||||
'is_checkout_block_customer_signup' => true,
|
||||
'user_login' => $username,
|
||||
'user_pass' => $password,
|
||||
'user_email' => $user_email,
|
||||
'first_name' => $first_name,
|
||||
'last_name' => $last_name,
|
||||
'role' => 'customer',
|
||||
)
|
||||
);
|
||||
|
||||
$customer_id = wp_insert_user( $new_customer_data );
|
||||
|
||||
if ( is_wp_error( $customer_id ) ) {
|
||||
throw $this->map_create_account_error( $customer_id );
|
||||
}
|
||||
|
||||
// Set account flag to remind customer to update generated password.
|
||||
update_user_option( $customer_id, 'default_password_nag', true, true );
|
||||
|
||||
do_action( 'woocommerce_created_customer', $customer_id, $new_customer_data, $password_generated );
|
||||
|
||||
return $customer_id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services\Email;
|
||||
|
||||
use \WP_User;
|
||||
use \WC_Email;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
|
||||
/**
|
||||
* Customer New Account.
|
||||
*
|
||||
* An email sent to the customer when they create an account.
|
||||
* This is intended as a replacement to WC_Email_Customer_New_Account(),
|
||||
* with a set password link instead of emailing the new password in email
|
||||
* content.
|
||||
*
|
||||
* @extends WC_Email
|
||||
*/
|
||||
class CustomerNewAccount extends \WC_Email {
|
||||
|
||||
/**
|
||||
* User login name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $user_login;
|
||||
|
||||
/**
|
||||
* User email.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $user_email;
|
||||
|
||||
/**
|
||||
* Magic link to set initial password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $set_password_url;
|
||||
|
||||
/**
|
||||
* Override (force) default template path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $default_template_path;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Package $package An instance of (Woo Blocks) Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
// Note - we're using the same ID as the real email.
|
||||
// This ensures that any merchant tweaks (Settings > Emails)
|
||||
// apply to this email (consistent with the core email).
|
||||
$this->id = 'customer_new_account';
|
||||
$this->customer_email = true;
|
||||
$this->title = __( 'New account', 'woo-gutenberg-products-block' );
|
||||
$this->description = __( 'Customer "new account" emails are sent to the customer when a customer signs up via checkout or account blocks.', 'woo-gutenberg-products-block' );
|
||||
$this->template_html = 'emails/customer-new-account-blocks.php';
|
||||
$this->template_plain = 'emails/plain/customer-new-account-blocks.php';
|
||||
$this->default_template_path = $package->get_path( '/templates/' );
|
||||
|
||||
// Call parent constructor.
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email subject.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_subject() {
|
||||
return __( 'Your {site_title} account has been created!', 'woo-gutenberg-products-block' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email heading.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_heading() {
|
||||
return __( 'Welcome to {site_title}', 'woo-gutenberg-products-block' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $user_pass User password.
|
||||
* @param bool $password_generated Whether the password was generated automatically or not.
|
||||
*/
|
||||
public function trigger( $user_id, $user_pass = '', $password_generated = false ) {
|
||||
$this->setup_locale();
|
||||
|
||||
if ( $user_id ) {
|
||||
$this->object = new \WP_User( $user_id );
|
||||
|
||||
// Generate a magic link so user can set initial password.
|
||||
$key = get_password_reset_key( $this->object );
|
||||
if ( ! is_wp_error( $key ) ) {
|
||||
$this->set_password_url = network_site_url(
|
||||
"wp-login.php?action=rp&key=$key&login=" . rawurlencode( $this->object->user_login ),
|
||||
'login'
|
||||
);
|
||||
}
|
||||
|
||||
$this->user_login = stripslashes( $this->object->user_login );
|
||||
$this->user_email = stripslashes( $this->object->user_email );
|
||||
$this->recipient = $this->user_email;
|
||||
}
|
||||
|
||||
if ( $this->is_enabled() && $this->get_recipient() ) {
|
||||
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments(), $this->set_password_url );
|
||||
}
|
||||
|
||||
$this->restore_locale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content html.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content_html() {
|
||||
return wc_get_template_html(
|
||||
$this->template_html,
|
||||
array(
|
||||
'email_heading' => $this->get_heading(),
|
||||
'additional_content' => $this->get_additional_content(),
|
||||
'user_login' => $this->user_login,
|
||||
'blogname' => $this->get_blogname(),
|
||||
'set_password_url' => $this->set_password_url,
|
||||
'sent_to_admin' => false,
|
||||
'plain_text' => false,
|
||||
'email' => $this,
|
||||
),
|
||||
'',
|
||||
$this->default_template_path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content plain.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content_plain() {
|
||||
return wc_get_template_html(
|
||||
$this->template_plain,
|
||||
array(
|
||||
'email_heading' => $this->get_heading(),
|
||||
'additional_content' => $this->get_additional_content(),
|
||||
'user_login' => $this->user_login,
|
||||
'blogname' => $this->get_blogname(),
|
||||
'set_password_url' => $this->set_password_url,
|
||||
'sent_to_admin' => false,
|
||||
'plain_text' => true,
|
||||
'email' => $this,
|
||||
),
|
||||
'',
|
||||
$this->default_template_path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default content to show below main email content.
|
||||
*
|
||||
* @since 3.7.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_additional_content() {
|
||||
return __( 'We look forward to seeing you soon.', 'woo-gutenberg-products-block' );
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use \Exception;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\OrderController;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\ReserveStock;
|
||||
|
@ -161,6 +164,16 @@ class Checkout extends AbstractRoute {
|
|||
// Check order is still valid.
|
||||
$order_controller->validate_order_before_payment( $order_object );
|
||||
|
||||
// Create a new user account as necessary.
|
||||
// Note - CreateAccount class includes feature gating logic (i.e. this
|
||||
// may not create an account depending on build).
|
||||
try {
|
||||
$create_account = Package::container()->get( CreateAccount::class );
|
||||
$create_account->from_order_request( $order_object, $request );
|
||||
} catch ( Exception $error ) {
|
||||
$this->handle_error( $error );
|
||||
}
|
||||
|
||||
// Persist customer address data to account.
|
||||
$order_controller->sync_customer_data_with_order( $order_object );
|
||||
|
||||
|
@ -314,6 +327,34 @@ class Checkout extends AbstractRoute {
|
|||
return $order_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an account creation error to a Store API error.
|
||||
*
|
||||
* @param \Exception $error Caught exception.
|
||||
*
|
||||
* @throws RouteException API error object with error details.
|
||||
*/
|
||||
private function handle_error( Exception $error ) {
|
||||
switch ( $error->getMessage() ) {
|
||||
case 'registration-error-invalid-email':
|
||||
throw new RouteException(
|
||||
'registration-error-invalid-email',
|
||||
__( 'Please provide a valid email address.', 'woo-gutenberg-products-block' ),
|
||||
400
|
||||
);
|
||||
|
||||
case 'registration-error-email-exists':
|
||||
throw new RouteException(
|
||||
'registration-error-email-exists',
|
||||
apply_filters(
|
||||
'woocommerce_registration_error_email_exists',
|
||||
__( 'An account is already registered with your email address. Please log in.', 'woo-gutenberg-products-block' )
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an order using the posted values from the request.
|
||||
*
|
||||
|
|
|
@ -94,6 +94,11 @@ class CheckoutSchema extends AbstractSchema {
|
|||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'create_account' => [
|
||||
'description' => __( 'Whether to create a new user account as part of order processing.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'boolean',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'payment_result' => [
|
||||
'description' => __( 'Result of payment processing, or false if not yet processed.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/**
|
||||
* Customer new account email - html.
|
||||
*
|
||||
* This is intended as a replacement to WC_Email_Customer_New_Account(),
|
||||
* with a set password link instead of including the new password in email
|
||||
* content.
|
||||
*
|
||||
* @package WooCommerce/Blocks
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
do_action( 'woocommerce_email_header', $email_heading, $email ); ?>
|
||||
|
||||
<?php /* translators: %s: Customer username */ ?>
|
||||
<p><?php printf( esc_html__( 'Hello %s,', 'woo-gutenberg-products-block' ), esc_html( $user_login ) ); ?></p>
|
||||
<?php /* translators: %1$s: Site title, %2$s: Username, %3$s: My account link */ ?>
|
||||
<p><?php printf( esc_html__( 'Thanks for creating an account on %1$s. Your username is %2$s. You can access your account area to view orders, change your password, and more at: %3$s', 'woo-gutenberg-products-block' ), esc_html( $blogname ), '<strong>' . esc_html( $user_login ) . '</strong>', make_clickable( esc_url( wc_get_page_permalink( 'myaccount' ) ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
|
||||
<?php if ( $set_password_url ) : ?>
|
||||
<p><a href="<?php echo esc_attr( $set_password_url ); ?>"><?php printf( esc_html__( 'Click here to set your new password.', 'woo-gutenberg-products-block' ) ); ?></a></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Show user-defined additional content - this is set in each email's settings.
|
||||
*/
|
||||
if ( $additional_content ) {
|
||||
echo wp_kses_post( wpautop( wptexturize( $additional_content ) ) );
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_email_footer', $email );
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/**
|
||||
* Customer new account email - text.
|
||||
*
|
||||
* This is intended as a replacement to WC_Email_Customer_New_Account(),
|
||||
* with a set password link instead of including the new password in email
|
||||
* content.
|
||||
*
|
||||
* @package WooCommerce/Blocks
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
|
||||
echo esc_html( wp_strip_all_tags( $email_heading ) );
|
||||
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
|
||||
|
||||
/* translators: %s: Customer username */
|
||||
echo sprintf( esc_html__( 'Hi %s,', 'woo-gutenberg-products-block' ), esc_html( $user_login ) ) . "\n\n";
|
||||
/* translators: %1$s: Site title, %2$s: Username, %3$s: My account link */
|
||||
echo sprintf( esc_html__( 'Thanks for creating an account on %1$s. Your username is %2$s. You can access your account area to view orders, change your password, and more at: %3$s', 'woo-gutenberg-products-block' ), esc_html( $blogname ), esc_html( $user_login ), esc_html( wc_get_page_permalink( 'myaccount' ) ) ) . "\n\n";
|
||||
|
||||
if ( $set_password_url ) {
|
||||
echo esc_html__( 'To set your password, visit the following address: ', 'woo-gutenberg-products-block' ) . "\n\n";
|
||||
echo esc_html( $set_password_url ) . "\n\n";
|
||||
}
|
||||
|
||||
echo "\n\n----------------------------------------\n\n";
|
||||
|
||||
/**
|
||||
* Show user-defined additional content - this is set in each email's settings.
|
||||
*/
|
||||
if ( $additional_content ) {
|
||||
echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) );
|
||||
echo "\n\n----------------------------------------\n\n";
|
||||
}
|
||||
|
||||
echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) );
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Tests\Library;
|
||||
|
||||
use \WP_UnitTestCase;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount as TestedCreateAccount;
|
||||
|
||||
/**
|
||||
* Tests CreateAccount service class.
|
||||
*
|
||||
* Note: this feature is currently feature gated. This test class assumes
|
||||
* that woocommerce_blocks_phase===3, i.e. dev build. Tests will fail
|
||||
* with other builds (release feature plugin, woo core package).
|
||||
* Related: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/3211
|
||||
*
|
||||
* @since $VID:$
|
||||
*/
|
||||
class CreateAccount extends WP_UnitTestCase {
|
||||
|
||||
private function get_test_instance() {
|
||||
return new TestedCreateAccount( new Package( 'test', './' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generalised routine for setting up test input and store state
|
||||
* and calling from_order_request. Used for all tests.
|
||||
*
|
||||
* Note – this requires (assumes) that there is no logged-in user.
|
||||
*
|
||||
* @return assoc array with keys [ 'user_id', 'order' ] if successful.
|
||||
*/
|
||||
private function execute_create_customer_from_order( $email, $first_name, $last_name, $options = [] ) {
|
||||
/// -- Test-specific setup start.
|
||||
|
||||
$tmp_enable_guest_checkout = get_option( 'woocommerce_enable_guest_checkout' );
|
||||
$enable_guest_checkout = array_key_exists( 'enable_guest_checkout', $options ) ? $options['enable_guest_checkout'] : false;
|
||||
update_option( 'woocommerce_enable_guest_checkout', $enable_guest_checkout );
|
||||
|
||||
$test_request = new \WP_REST_Request();
|
||||
$should_create_account = array_key_exists( 'should_create_account', $options ) ? $options['should_create_account'] : false;
|
||||
$test_request->set_param( 'should_create_account', $should_create_account );
|
||||
|
||||
$test_order = new \WC_Order();
|
||||
$test_order->set_billing_email( $email );
|
||||
$test_order->set_billing_first_name( $first_name );
|
||||
$test_order->set_billing_last_name( $last_name );
|
||||
|
||||
/// -- End test-specific setup.
|
||||
|
||||
$user_id = $this->get_test_instance()->from_order_request( $test_order, $test_request );
|
||||
|
||||
/// -- Undo test-specific setup; restore previous state.
|
||||
update_option( 'woocommerce_enable_guest_checkout', $tmp_enable_guest_checkout );
|
||||
|
||||
return [
|
||||
'user_id' => $user_id,
|
||||
'order' => $test_order,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful user signup cases.
|
||||
*
|
||||
* @dataProvider create_customer_data
|
||||
*/
|
||||
public function test_create_customer_from_order( $email, $first_name, $last_name, $options ) {
|
||||
$result = $this->execute_create_customer_from_order(
|
||||
$email,
|
||||
$first_name,
|
||||
$last_name,
|
||||
$options
|
||||
);
|
||||
|
||||
$test_user = $this->factory()->user->get_object_by_id( $result['user_id'] );
|
||||
$test_order = $result['order'];
|
||||
|
||||
$this->assertEquals( get_current_user_id(), $result['user_id'] );
|
||||
|
||||
$this->assertEquals( $test_user->first_name, $first_name );
|
||||
$this->assertEquals( $test_user->last_name, $last_name );
|
||||
$this->assertEquals( $test_user->user_email, $email );
|
||||
$this->assertArraySubset( $test_user->roles, [ 'customer' ] );
|
||||
|
||||
$this->assertEquals( $test_order->get_customer_id(), $result['user_id'] );
|
||||
}
|
||||
|
||||
public function create_customer_data() {
|
||||
return [
|
||||
// User requested an account.
|
||||
[
|
||||
'maryjones@testperson.net',
|
||||
'Mary',
|
||||
'Jones',
|
||||
[
|
||||
'should_create_account' => true,
|
||||
'enable_guest_checkout' => true,
|
||||
],
|
||||
],
|
||||
// User requested an account + site doesn't allow guest.
|
||||
[
|
||||
'maryjones@testperson.net',
|
||||
'Mary',
|
||||
'Jones',
|
||||
[
|
||||
'should_create_account' => true,
|
||||
'enable_guest_checkout' => false,
|
||||
],
|
||||
],
|
||||
// User requested an account; name fields are not required.
|
||||
[
|
||||
'private_person@hotmail.com',
|
||||
'',
|
||||
'',
|
||||
[
|
||||
'should_create_account' => true,
|
||||
'enable_guest_checkout' => true,
|
||||
],
|
||||
],
|
||||
// Store does not allow guest - signup is required (automatic).
|
||||
[
|
||||
'henrykissinger@fbi.gov',
|
||||
'Henry',
|
||||
'Kissinger',
|
||||
[
|
||||
'should_create_account' => false,
|
||||
'enable_guest_checkout' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test exception is thrown if user already signed up.
|
||||
*/
|
||||
public function test_customer_already_exists() {
|
||||
$user_id = $this->factory()->user->create( [
|
||||
'user_email' => 'maryjones@testperson.net',
|
||||
] );
|
||||
|
||||
$this->expectException( \Exception::class );
|
||||
|
||||
$result = $this->execute_create_customer_from_order(
|
||||
'maryjones@testperson.net',
|
||||
'Mary',
|
||||
'Jones',
|
||||
[
|
||||
'should_create_account' => true,
|
||||
'enable_guest_checkout' => true,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test exception is thrown if email is invalid or malformed.
|
||||
*
|
||||
* @dataProvider invalid_email_data
|
||||
*/
|
||||
public function test_invalid_email( $email ) {
|
||||
$this->expectException( \Exception::class );
|
||||
|
||||
$result = $this->execute_create_customer_from_order(
|
||||
$email,
|
||||
'Mary',
|
||||
'Jones',
|
||||
[
|
||||
'should_create_account' => true,
|
||||
'enable_guest_checkout' => true,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function invalid_email_data() {
|
||||
return [
|
||||
[ 'maryjones AT testperson DOT net' ],
|
||||
[ 'lean@fast' ],
|
||||
[ '' ],
|
||||
[ ' ' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a user is not created if not requested (and the site allows guest checkout).
|
||||
*/
|
||||
public function test_no_account_requested() {
|
||||
$site_user_counts = count_users();
|
||||
|
||||
$result = $this->execute_create_customer_from_order(
|
||||
'maryjones@testperson.net',
|
||||
'Mary',
|
||||
'Jones',
|
||||
[
|
||||
'should_create_account' => false,
|
||||
'enable_guest_checkout' => true,
|
||||
],
|
||||
);
|
||||
|
||||
$site_user_counts_after = count_users();
|
||||
|
||||
$this->assertEquals( $site_user_counts['total_users'], $site_user_counts_after['total_users'] );
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue