PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.4.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.4.0
2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 All 65 releases
subscription / includes / Illuminate / GuestCheckout.php

GuestCheckout.php in Subscriptions for WooCommerce with Stripe Recurring Payments 2.4.0, at includes/Illuminate/GuestCheckout.php

519 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Guest Checkout File
4 *
5 * @package SpringDevs\Subscription\Illuminate
6 */
7
8 namespace SpringDevs\Subscription\Illuminate;
9
10 /**
11 * Class GuestCheckout
12 *
13 * @package SpringDevs\Subscription\Illuminate
14 */
15 class GuestCheckout {
16 /**
17 * Initialize the class
18 */
19 public function __construct() {
20 // Add guest checkout settings
21 add_filter( 'subscrpt_settings_fields', [ $this, 'add_guest_checkout_settings_fields' ] );
22 add_action( 'subscrpt_register_settings', [ $this, 'register_settings' ] );
23
24 // Show warning if guest checkout is disabled in WooCommerce settings.
25 add_action( 'admin_notices', [ $this, 'check_woocommerce_checkout_settings' ] );
26
27 // Guest checkout validation.
28 add_action( 'woocommerce_checkout_process', [ $this, 'validate_guest_checkout' ] );
29 add_action( 'woocommerce_store_api_cart_errors', [ $this, 'validate_guest_checkout_storeapi' ] );
30
31 // Enforce Login/Registration in checkout
32 add_action( 'woocommerce_checkout_process', [ $this, 'require_account_creation' ] );
33 add_filter( 'woocommerce_store_api_checkout_update_order_from_request', [ $this,'require_account_creation_store_api' ], 10, 2 );
34
35 // Guest account creation.
36 add_action( 'woocommerce_store_api_checkout_update_customer_from_request', [ $this, 'maybe_create_user_from_customer' ], 10, 1 );
37 add_action( 'woocommerce_store_api_checkout_update_order_from_request', [ $this, 'maybe_assign_user_to_order' ], 5, 1 );
38 add_action( 'woocommerce_checkout_create_order', [ $this, 'maybe_assign_user_to_order' ], 10, 1 );
39 }
40
41 /**
42 * Add Guest Checkout Settings Fields
43 *
44 * @param array $settings_fields Settings fields.
45 * @return array
46 */
47 public function add_guest_checkout_settings_fields( $settings_fields ) {
48 $guest_checkout_fields = [
49 [
50 'type' => 'heading',
51 'group' => 'guest_checkout',
52 'priority' => 2,
53 'field_data' => [
54 'title' => __( 'Guest Checkout', 'subscription' ),
55 'description' => __( 'Manage guest checkout settings for subscriptions.', 'subscription' ),
56 ],
57 ],
58 [
59 'type' => 'toggle',
60 'group' => 'guest_checkout',
61 'priority' => 1,
62 'field_data' => [
63 'id' => 'wp_subscription_allow_guest_checkout',
64 'title' => __( 'Allow Guest Checkout', 'subscription' ),
65 'description' => __( 'Allow customers to checkout without logging in.', 'subscription' ) . '<br/><sub>' . __( 'Note: You will need to enable <strong>Guest checkout</strong> and <strong>Allow customers to create an account during checkout</strong> options in WooCommerce settings for this to work properly.', 'subscription' ) . '</sub>',
66 'value' => '1',
67 'checked' => '1' === get_option( 'wp_subscription_allow_guest_checkout', '0' ),
68 ],
69 ],
70 [
71 'type' => 'toggle',
72 'group' => 'guest_checkout',
73 'priority' => 2,
74 'field_data' => [
75 'id' => 'wp_subscription_enforce_login',
76 'title' => __( 'Enforce Login', 'subscription' ),
77 'description' => __( 'Force customers to login or check the "Create account" checkbox before checking out.', 'subscription' ),
78 'value' => '1',
79 'checked' => '1' === get_option( 'wp_subscription_enforce_login', '1' ),
80 ],
81 ],
82 ];
83
84 return array_merge( $settings_fields, $guest_checkout_fields );
85 }
86
87 /**
88 * Register settings option.
89 */
90 public function register_settings() {
91 register_setting(
92 'wp_subscription_settings',
93 'wp_subscription_allow_guest_checkout',
94 array(
95 'type' => 'string',
96 'sanitize_callback' => 'sanitize_text_field',
97 )
98 );
99
100 register_setting(
101 'wp_subscription_settings',
102 'wp_subscription_enforce_login',
103 array(
104 'type' => 'string',
105 'sanitize_callback' => 'sanitize_text_field',
106 )
107 );
108 }
109
110 /**
111 * Is guest checkout allowed
112 */
113 public static function is_guest_checkout_allowed() {
114 return in_array( get_option( 'wp_subscription_allow_guest_checkout', '0' ), [ 1, '1' ], true );
115 }
116
117 /**
118 * Is guest login enforced
119 */
120 public static function is_guest_login_enforced() {
121 return in_array( get_option( 'wp_subscription_enforce_login', '1' ), [ 1, '1' ], true );
122 }
123
124 /**
125 * Check if cart/order has subscription and guest checkout are allowed.
126 */
127 public function is_subs_and_guest_checkout_allowed() {
128 $is_user_logged_in = is_user_logged_in();
129 $is_guest_checkout_allowed = self::is_guest_checkout_allowed();
130 $cart_have_subscription = false;
131
132 // Check in cart.
133 if ( function_exists( 'WC' ) ) {
134 $cart_items = WC()->cart->get_cart();
135 $recurrs = Helper::get_recurrs_from_cart( $cart_items );
136 $cart_have_subscription = count( $recurrs ) > 0;
137 }
138
139 if ( $cart_have_subscription ) {
140 return $is_user_logged_in || $is_guest_checkout_allowed;
141 } else {
142 return true;
143 }
144 }
145
146 /**
147 * Check WooCommerce checkout settings and show admin notice if guest checkout is disabled.
148 */
149 public function check_woocommerce_checkout_settings() {
150 // Check if WooCommerce is active
151 if ( ! class_exists( 'WooCommerce' ) ) {
152 return;
153 }
154
155 $subs_guest_checkout_allowed = self::is_guest_checkout_allowed();
156 if ( ! $subs_guest_checkout_allowed ) {
157 return;
158 }
159
160 $guest_checkout_enabled = in_array( get_option( 'woocommerce_enable_guest_checkout' ), [ 1, '1', 'yes', 'on' ], true );
161 $account_during_checkout = in_array( get_option( 'woocommerce_enable_signup_and_login_from_checkout' ), [ 1, '1', 'yes', 'on' ], true );
162 $account_after_checkout = in_array( get_option( 'woocommerce_enable_delayed_account_creation' ), [ 1, '1', 'yes', 'on' ], true );
163
164 $issues = [];
165 if ( ! $guest_checkout_enabled ) {
166 $issues[] = __( 'Guest checkout.', 'subscription' );
167 }
168 if ( ! $account_during_checkout ) {
169 $issues[] = __( 'Account creation during checkout.', 'subscription' );
170 }
171
172 if ( ! empty( $issues ) ) {
173 $settings_url = admin_url( 'admin.php?page=wc-settings&tab=account' );
174
175 $list_html = '';
176 foreach ( $issues as $issue ) {
177 $list_html .= '<li><span class="dashicons dashicons-arrow-right"></span> <strong>' . $issue . '</strong></li>';
178 }
179
180 $requirement_html = '<div class="notice notice-error is-dismissible">' .
181 '<p>' . sprintf(
182 /* translators: %s: URL of the WooCommerce account settings. */
183 __( 'To ensure Subscriptions guest checkout functions correctly, please enable the following settings in WooCommerce. Click <a href="%s">here</a> to go to the settings.', 'subscription' ),
184 esc_url( $settings_url )
185 ) . '</p>' .
186 '<ul>' . $list_html . '</ul></div>';
187
188 echo wp_kses_post( $requirement_html );
189 }
190
191 if ( $account_after_checkout ) {
192 $settings_url = admin_url( 'admin.php?page=wc-settings&tab=account' );
193
194 $requirement_html = '<div class="notice notice-warning is-dismissible">' .
195 '<p>' . __( 'Enabling <strong>Account creation after checkout</strong> in WooCommerce settings may lead to issues with subscription orders for guest users.', 'subscription' ) . '</p>' .
196 '<p>' . sprintf(
197 /* translators: %s: URL of the WooCommerce account settings. */
198 __( 'It\'s recommended to disable this option for optimal functionality with Subscriptions. Click <a href="%s">here</a> to go to the settings.', 'subscription' ),
199 esc_url( $settings_url )
200 ) . '</p></div>';
201
202 echo wp_kses_post( $requirement_html );
203 }
204 }
205
206 /**
207 * Validate guest checkout.
208 */
209 public function validate_guest_checkout() {
210 if ( ! $this->is_subs_and_guest_checkout_allowed() ) {
211 wc_add_notice( __( 'You are trying to buy a subscription. You must be logged in to continue.', 'subscription' ), 'error' );
212 return;
213 }
214 }
215
216 /**
217 * Validate guest checkout on storeAPI.
218 *
219 * @param \WP_Error $errors Errors object.
220 * @return \WP_Error
221 */
222 public function validate_guest_checkout_storeapi( $errors ) {
223 if ( ! $this->is_subs_and_guest_checkout_allowed() ) {
224 $errors->add( 'wp_subscription_login_required', __( 'You are trying to buy a subscription. You must be logged in to continue.', 'subscription' ) );
225 return $errors;
226 }
227 }
228
229 /**
230 * Enforce account creation in checkout.
231 */
232 public function require_account_creation() {
233 if ( is_user_logged_in() || ! self::is_guest_checkout_allowed() ) {
234 return;
235 }
236
237 // Check cart for subscriptions.
238 $cart_have_subscription = false;
239 if ( function_exists( 'WC' ) ) {
240 $cart_items = WC()->cart->get_cart();
241 $recurrs = Helper::get_recurrs_from_cart( $cart_items );
242 $cart_have_subscription = count( $recurrs ) > 0;
243 }
244
245 if ( ! $cart_have_subscription ) {
246 return;
247 }
248
249 // phpcs:ignore WordPress.Security.NonceVerification
250 $is_create_account = isset( $_POST['createaccount'] ) ? (bool) sanitize_text_field( wp_unslash( $_POST['createaccount'] ) ) : false;
251 $is_login_enforced = self::is_guest_login_enforced();
252
253 if ( $is_login_enforced && ! $is_create_account ) {
254 wc_add_notice(
255 wp_kses_post(
256 __( 'You are ordering a subscription product. You must be either <strong>logged in</strong> or check the "<strong>Create an account</strong>" option to continue the checkout.', 'subscription' )
257 ),
258 'error'
259 );
260 }
261 }
262
263 /**
264 * Enforce account creation in Store API checkout.
265 *
266 * @param WC_Order $order Order object.
267 * @param WP_REST_Request $request Request object.
268 *
269 * @throws \WC_Data_Exception If account creation is required but not selected.
270 */
271 public function require_account_creation_store_api( $order, $request ) {
272 if ( is_user_logged_in() || ! self::is_guest_checkout_allowed() ) {
273 return;
274 }
275
276 $is_subscription_order = Helper::order_has_subscription_item( $order );
277 if ( ! $is_subscription_order ) {
278 return;
279 }
280
281 $request_body = $request->get_json_params();
282 $is_create_account = $request_body['create_account'] ?? false;
283 $is_login_enforced = self::is_guest_login_enforced();
284
285 if ( $is_login_enforced && ! $is_create_account ) {
286 throw new \WC_Data_Exception(
287 'wp_subscription_account_required',
288 wp_kses_post(
289 __( 'You are ordering a subscription product. You must be either <strong>logged in</strong> or check the "<strong>Create an account</strong>" option to continue the checkout.', 'subscription' )
290 ),
291 400
292 );
293 }
294 }
295
296 /**
297 * Build user info from order or customer object.
298 *
299 * @param \WC_Order|\WC_Customer $order_or_customer Order or Customer object.
300 */
301 public function build_user_info( $order_or_customer ): array {
302 $user_info = [];
303
304 // Billing info.
305 $user_info['billing_first_name'] = $order_or_customer->get_billing_first_name();
306 $user_info['billing_last_name'] = $order_or_customer->get_billing_last_name();
307 $user_info['billing_company'] = $order_or_customer->get_billing_company();
308 $user_info['billing_address_1'] = $order_or_customer->get_billing_address_1();
309 $user_info['billing_address_2'] = $order_or_customer->get_billing_address_2();
310 $user_info['billing_city'] = $order_or_customer->get_billing_city();
311 $user_info['billing_postcode'] = $order_or_customer->get_billing_postcode();
312 $user_info['billing_country'] = $order_or_customer->get_billing_country();
313 $user_info['billing_state'] = $order_or_customer->get_billing_state();
314 $user_info['billing_email'] = $order_or_customer->get_billing_email();
315 $user_info['billing_phone'] = $order_or_customer->get_billing_phone();
316
317 // Shipping info.
318 $user_info['shipping_first_name'] = ! empty( $order_or_customer->get_shipping_first_name() ) ? $order_or_customer->get_shipping_first_name() : $user_info['billing_first_name'];
319 $user_info['shipping_last_name'] = ! empty( $order_or_customer->get_shipping_last_name() ) ? $order_or_customer->get_shipping_last_name() : $user_info['billing_last_name'];
320 $user_info['shipping_company'] = ! empty( $order_or_customer->get_shipping_company() ) ? $order_or_customer->get_shipping_company() : $user_info['billing_company'];
321 $user_info['shipping_address_1'] = ! empty( $order_or_customer->get_shipping_address_1() ) ? $order_or_customer->get_shipping_address_1() : $user_info['billing_address_1'];
322 $user_info['shipping_address_2'] = ! empty( $order_or_customer->get_shipping_address_2() ) ? $order_or_customer->get_shipping_address_2() : $user_info['billing_address_2'];
323 $user_info['shipping_city'] = ! empty( $order_or_customer->get_shipping_city() ) ? $order_or_customer->get_shipping_city() : $user_info['billing_city'];
324 $user_info['shipping_postcode'] = ! empty( $order_or_customer->get_shipping_postcode() ) ? $order_or_customer->get_shipping_postcode() : $user_info['billing_postcode'];
325 $user_info['shipping_country'] = ! empty( $order_or_customer->get_shipping_country() ) ? $order_or_customer->get_shipping_country() : $user_info['billing_country'];
326 $user_info['shipping_state'] = ! empty( $order_or_customer->get_shipping_state() ) ? $order_or_customer->get_shipping_state() : $user_info['billing_state'];
327 $user_info['shipping_phone'] = ! empty( $order_or_customer->get_shipping_phone() ) ? $order_or_customer->get_shipping_phone() : $user_info['billing_phone'];
328
329 return $user_info;
330 }
331
332 /**
333 * Maybe create user from user info.
334 *
335 * @param array $user_info User info array.
336 */
337 public function maybe_create_user( $user_info ): ?int {
338 // Don't proceed if guest checkout is not allowed.
339 $is_guest_checkout_allowed = in_array( get_option( 'wp_subscription_allow_guest_checkout', '0' ), [ 1, '1', 'yes', 'on' ], true );
340 if ( ! $is_guest_checkout_allowed ) {
341 return null;
342 }
343
344 $is_new_customer = false;
345
346 // Check if user exists with email.
347 $user = get_user_by( 'email', $user_info['billing_email'] );
348 $user_id = $user ? $user->ID : 0;
349
350 if ( ! $user_id ) {
351 $is_new_customer = true;
352
353 // Not wp_insert_user(): without a `user_pass` it stores wp_hash_password( '' ).
354 $user_id = wc_create_new_customer(
355 $user_info['billing_email'],
356 '',
357 '',
358 [
359 'first_name' => $user_info['billing_first_name'],
360 'last_name' => $user_info['billing_last_name'],
361 'source' => 'subscrpt-guest-checkout',
362 ]
363 );
364
365 if ( is_wp_error( $user_id ) ) {
366 subscrpt_write_log( 'Failed to auto-create user during checkout. Error: ' . $user_id->get_error_message() );
367 return null;
368 }
369
370 // Set billing info.
371 update_user_meta( $user_id, 'billing_first_name', $user_info['billing_first_name'] );
372 update_user_meta( $user_id, 'billing_last_name', $user_info['billing_last_name'] );
373 update_user_meta( $user_id, 'billing_company', $user_info['billing_company'] );
374 update_user_meta( $user_id, 'billing_address_1', $user_info['billing_address_1'] );
375 update_user_meta( $user_id, 'billing_address_2', $user_info['billing_address_2'] );
376 update_user_meta( $user_id, 'billing_city', $user_info['billing_city'] );
377 update_user_meta( $user_id, 'billing_postcode', $user_info['billing_postcode'] );
378 update_user_meta( $user_id, 'billing_country', $user_info['billing_country'] );
379 update_user_meta( $user_id, 'billing_state', $user_info['billing_state'] );
380 update_user_meta( $user_id, 'billing_email', $user_info['billing_email'] );
381 update_user_meta( $user_id, 'billing_phone', $user_info['billing_phone'] );
382
383 // Set shipping info.
384 update_user_meta( $user_id, 'shipping_first_name', $user_info['shipping_first_name'] );
385 update_user_meta( $user_id, 'shipping_last_name', $user_info['shipping_last_name'] );
386 update_user_meta( $user_id, 'shipping_company', $user_info['shipping_company'] );
387 update_user_meta( $user_id, 'shipping_address_1', $user_info['shipping_address_1'] );
388 update_user_meta( $user_id, 'shipping_address_2', $user_info['shipping_address_2'] );
389 update_user_meta( $user_id, 'shipping_city', $user_info['shipping_city'] );
390 update_user_meta( $user_id, 'shipping_postcode', $user_info['shipping_postcode'] );
391 update_user_meta( $user_id, 'shipping_country', $user_info['shipping_country'] );
392 update_user_meta( $user_id, 'shipping_state', $user_info['shipping_state'] );
393 update_user_meta( $user_id, 'shipping_phone', $user_info['shipping_phone'] );
394 }
395
396 // Auto-login the newly created account so the subscription can be associated with the user.
397 // This ONLY runs when $is_new_customer is true — i.e. when wp_insert_user() succeeded
398 // just above in this same request. It never fires for returning/existing users.
399 // wc_set_customer_auth_cookie() is the WooCommerce-sanctioned way to log a customer in
400 // during checkout; wp_set_auth_cookie() is the WP fallback if WC is not available.
401 if ( ! is_user_logged_in() && $is_new_customer && $user_id ) {
402 wp_set_current_user( $user_id );
403 if ( function_exists( 'wc_set_customer_auth_cookie' ) ) {
404 wc_set_customer_auth_cookie( $user_id );
405 } else {
406 wp_set_auth_cookie( $user_id );
407 }
408
409 subscrpt_write_debug_log( 'Auto-logged in newly created user ID: ' . $user_id . ' after checkout.' );
410 }
411
412 return $user_id;
413 }
414
415 /**
416 * Auto-create a guest account only while login is not enforced.
417 *
418 * @return bool
419 */
420 protected function should_auto_create_account() {
421 return self::is_guest_checkout_allowed() && ! self::is_guest_login_enforced();
422 }
423
424 /**
425 * Does the current cart contain a subscription product.
426 *
427 * @return bool
428 */
429 protected function cart_has_subscription() {
430 if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
431 return false;
432 }
433
434 return count( Helper::get_recurrs_from_cart( WC()->cart->get_cart() ) ) > 0;
435 }
436
437 /**
438 * Create the guest account before the order exists, for gateway customer binding.
439 *
440 * @param \WC_Customer $customer Customer object.
441 *
442 * @return void
443 */
444 public function maybe_create_user_from_customer( $customer ) {
445 if ( is_user_logged_in() || ! $this->should_auto_create_account() ) {
446 return;
447 }
448
449 // Already backed by a real user.
450 if ( $customer->get_id() ) {
451 return;
452 }
453
454 if ( ! $this->cart_has_subscription() ) {
455 return;
456 }
457
458 $billing_email = $customer->get_billing_email();
459
460 // Cannot create an account without an email.
461 if ( empty( $billing_email ) ) {
462 return;
463 }
464
465 // Never $customer->set_id() + save() here: it blanks user_email.
466 $this->maybe_create_user( $this->build_user_info( $customer ) );
467 }
468
469 /**
470 * Assign a user to a guest subscription order, creating one if needed.
471 *
472 * @param \WC_Order $order Order object.
473 *
474 * @return void
475 */
476 public function maybe_assign_user_to_order( $order ) {
477 if ( ! $this->should_auto_create_account() ) {
478 return;
479 }
480
481 if ( ! $order instanceof \WC_Order ) {
482 return;
483 }
484
485 // Don't proceed if the order already has a user.
486 if ( $order->get_customer_id() ) {
487 return;
488 }
489
490 if ( ! $this->cart_has_subscription() ) {
491 return;
492 }
493
494 // Reuse the user created earlier in this request when there is one.
495 $user_id = get_current_user_id();
496
497 if ( ! $user_id ) {
498 $user_info = $this->build_user_info( $order );
499
500 if ( empty( $user_info['billing_email'] ) ) {
501 return;
502 }
503
504 $user_id = $this->maybe_create_user( $user_info );
505 }
506
507 if ( ! $user_id ) {
508 return;
509 }
510
511 $order->set_customer_id( $user_id );
512
513 // The classic checkout hands over an unsaved order and persists it itself.
514 if ( $order->get_id() ) {
515 $order->save();
516 }
517 }
518 }
519