PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.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 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
subscription / includes / Illuminate / GuestCheckout.php

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

512 lines 18.5 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.';
167 }
168 if ( ! $account_during_checkout ) {
169 $issues[] = 'Account creation during checkout.';
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>To ensure Subscriptions guest checkout functions correctly, please enable the following settings in WooCommerce. ' .
182 'Click <a href="' . $settings_url . '">here</a> to go to the settings.</p>' .
183 '<ul>' . $list_html . '</ul></div>';
184
185 echo wp_kses_post( $requirement_html );
186 }
187
188 if ( $account_after_checkout ) {
189 $settings_url = admin_url( 'admin.php?page=wc-settings&tab=account' );
190
191 $requirement_html = '<div class="notice notice-warning is-dismissible">' .
192 '<p>Enabling <strong>Account creation after checkout</strong> in WooCommerce settings may lead to issues with subscription orders for guest users.</p>' .
193 '<p>It\'s recommended to disable this option for optimal functionality with Subscriptions. Click <a href="' . $settings_url . '">here</a> to go to the settings.</p></div>';
194
195 echo wp_kses_post( $requirement_html );
196 }
197 }
198
199 /**
200 * Validate guest checkout.
201 */
202 public function validate_guest_checkout() {
203 if ( ! $this->is_subs_and_guest_checkout_allowed() ) {
204 wc_add_notice( __( 'You are trying to buy a subscription. You must be logged in to continue.', 'subscription' ), 'error' );
205 return;
206 }
207 }
208
209 /**
210 * Validate guest checkout on storeAPI.
211 *
212 * @param \WP_Error $errors Errors object.
213 * @return \WP_Error
214 */
215 public function validate_guest_checkout_storeapi( $errors ) {
216 if ( ! $this->is_subs_and_guest_checkout_allowed() ) {
217 $errors->add( 'wp_subscription_login_required', __( 'You are trying to buy a subscription. You must be logged in to continue.', 'subscription' ) );
218 return $errors;
219 }
220 }
221
222 /**
223 * Enforce account creation in checkout.
224 */
225 public function require_account_creation() {
226 if ( is_user_logged_in() || ! self::is_guest_checkout_allowed() ) {
227 return;
228 }
229
230 // Check cart for subscriptions.
231 $cart_have_subscription = false;
232 if ( function_exists( 'WC' ) ) {
233 $cart_items = WC()->cart->get_cart();
234 $recurrs = Helper::get_recurrs_from_cart( $cart_items );
235 $cart_have_subscription = count( $recurrs ) > 0;
236 }
237
238 if ( ! $cart_have_subscription ) {
239 return;
240 }
241
242 // phpcs:ignore WordPress.Security.NonceVerification
243 $is_create_account = isset( $_POST['createaccount'] ) ? (bool) sanitize_text_field( wp_unslash( $_POST['createaccount'] ) ) : false;
244 $is_login_enforced = self::is_guest_login_enforced();
245
246 if ( $is_login_enforced && ! $is_create_account ) {
247 wc_add_notice(
248 wp_kses_post(
249 __( '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' )
250 ),
251 'error'
252 );
253 }
254 }
255
256 /**
257 * Enforce account creation in Store API checkout.
258 *
259 * @param WC_Order $order Order object.
260 * @param WP_REST_Request $request Request object.
261 *
262 * @throws \WC_Data_Exception If account creation is required but not selected.
263 */
264 public function require_account_creation_store_api( $order, $request ) {
265 if ( is_user_logged_in() || ! self::is_guest_checkout_allowed() ) {
266 return;
267 }
268
269 $is_subscription_order = Helper::order_has_subscription_item( $order );
270 if ( ! $is_subscription_order ) {
271 return;
272 }
273
274 $request_body = $request->get_json_params();
275 $is_create_account = $request_body['create_account'] ?? false;
276 $is_login_enforced = self::is_guest_login_enforced();
277
278 if ( $is_login_enforced && ! $is_create_account ) {
279 throw new \WC_Data_Exception(
280 'wp_subscription_account_required',
281 wp_kses_post(
282 __( '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' )
283 ),
284 400
285 );
286 }
287 }
288
289 /**
290 * Build user info from order or customer object.
291 *
292 * @param \WC_Order|\WC_Customer $order_or_customer Order or Customer object.
293 */
294 public function build_user_info( $order_or_customer ): array {
295 $user_info = [];
296
297 // Billing info.
298 $user_info['billing_first_name'] = $order_or_customer->get_billing_first_name();
299 $user_info['billing_last_name'] = $order_or_customer->get_billing_last_name();
300 $user_info['billing_company'] = $order_or_customer->get_billing_company();
301 $user_info['billing_address_1'] = $order_or_customer->get_billing_address_1();
302 $user_info['billing_address_2'] = $order_or_customer->get_billing_address_2();
303 $user_info['billing_city'] = $order_or_customer->get_billing_city();
304 $user_info['billing_postcode'] = $order_or_customer->get_billing_postcode();
305 $user_info['billing_country'] = $order_or_customer->get_billing_country();
306 $user_info['billing_state'] = $order_or_customer->get_billing_state();
307 $user_info['billing_email'] = $order_or_customer->get_billing_email();
308 $user_info['billing_phone'] = $order_or_customer->get_billing_phone();
309
310 // Shipping info.
311 $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'];
312 $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'];
313 $user_info['shipping_company'] = ! empty( $order_or_customer->get_shipping_company() ) ? $order_or_customer->get_shipping_company() : $user_info['billing_company'];
314 $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'];
315 $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'];
316 $user_info['shipping_city'] = ! empty( $order_or_customer->get_shipping_city() ) ? $order_or_customer->get_shipping_city() : $user_info['billing_city'];
317 $user_info['shipping_postcode'] = ! empty( $order_or_customer->get_shipping_postcode() ) ? $order_or_customer->get_shipping_postcode() : $user_info['billing_postcode'];
318 $user_info['shipping_country'] = ! empty( $order_or_customer->get_shipping_country() ) ? $order_or_customer->get_shipping_country() : $user_info['billing_country'];
319 $user_info['shipping_state'] = ! empty( $order_or_customer->get_shipping_state() ) ? $order_or_customer->get_shipping_state() : $user_info['billing_state'];
320 $user_info['shipping_phone'] = ! empty( $order_or_customer->get_shipping_phone() ) ? $order_or_customer->get_shipping_phone() : $user_info['billing_phone'];
321
322 return $user_info;
323 }
324
325 /**
326 * Maybe create user from user info.
327 *
328 * @param array $user_info User info array.
329 */
330 public function maybe_create_user( $user_info ): ?int {
331 // Don't proceed if guest checkout is not allowed.
332 $is_guest_checkout_allowed = in_array( get_option( 'wp_subscription_allow_guest_checkout', '0' ), [ 1, '1', 'yes', 'on' ], true );
333 if ( ! $is_guest_checkout_allowed ) {
334 return null;
335 }
336
337 $is_new_customer = false;
338
339 // Check if user exists with email.
340 $user = get_user_by( 'email', $user_info['billing_email'] );
341 $user_id = $user ? $user->ID : 0;
342
343 if ( ! $user_id ) {
344 $is_new_customer = true;
345
346 // Not wp_insert_user(): without a `user_pass` it stores wp_hash_password( '' ).
347 $user_id = wc_create_new_customer(
348 $user_info['billing_email'],
349 '',
350 '',
351 [
352 'first_name' => $user_info['billing_first_name'],
353 'last_name' => $user_info['billing_last_name'],
354 'source' => 'subscrpt-guest-checkout',
355 ]
356 );
357
358 if ( is_wp_error( $user_id ) ) {
359 subscrpt_write_log( 'Failed to auto-create user during checkout. Error: ' . $user_id->get_error_message() );
360 return null;
361 }
362
363 // Set billing info.
364 update_user_meta( $user_id, 'billing_first_name', $user_info['billing_first_name'] );
365 update_user_meta( $user_id, 'billing_last_name', $user_info['billing_last_name'] );
366 update_user_meta( $user_id, 'billing_company', $user_info['billing_company'] );
367 update_user_meta( $user_id, 'billing_address_1', $user_info['billing_address_1'] );
368 update_user_meta( $user_id, 'billing_address_2', $user_info['billing_address_2'] );
369 update_user_meta( $user_id, 'billing_city', $user_info['billing_city'] );
370 update_user_meta( $user_id, 'billing_postcode', $user_info['billing_postcode'] );
371 update_user_meta( $user_id, 'billing_country', $user_info['billing_country'] );
372 update_user_meta( $user_id, 'billing_state', $user_info['billing_state'] );
373 update_user_meta( $user_id, 'billing_email', $user_info['billing_email'] );
374 update_user_meta( $user_id, 'billing_phone', $user_info['billing_phone'] );
375
376 // Set shipping info.
377 update_user_meta( $user_id, 'shipping_first_name', $user_info['shipping_first_name'] );
378 update_user_meta( $user_id, 'shipping_last_name', $user_info['shipping_last_name'] );
379 update_user_meta( $user_id, 'shipping_company', $user_info['shipping_company'] );
380 update_user_meta( $user_id, 'shipping_address_1', $user_info['shipping_address_1'] );
381 update_user_meta( $user_id, 'shipping_address_2', $user_info['shipping_address_2'] );
382 update_user_meta( $user_id, 'shipping_city', $user_info['shipping_city'] );
383 update_user_meta( $user_id, 'shipping_postcode', $user_info['shipping_postcode'] );
384 update_user_meta( $user_id, 'shipping_country', $user_info['shipping_country'] );
385 update_user_meta( $user_id, 'shipping_state', $user_info['shipping_state'] );
386 update_user_meta( $user_id, 'shipping_phone', $user_info['shipping_phone'] );
387 }
388
389 // Auto-login the newly created account so the subscription can be associated with the user.
390 // This ONLY runs when $is_new_customer is true — i.e. when wp_insert_user() succeeded
391 // just above in this same request. It never fires for returning/existing users.
392 // wc_set_customer_auth_cookie() is the WooCommerce-sanctioned way to log a customer in
393 // during checkout; wp_set_auth_cookie() is the WP fallback if WC is not available.
394 if ( ! is_user_logged_in() && $is_new_customer && $user_id ) {
395 wp_set_current_user( $user_id );
396 if ( function_exists( 'wc_set_customer_auth_cookie' ) ) {
397 wc_set_customer_auth_cookie( $user_id );
398 } else {
399 wp_set_auth_cookie( $user_id );
400 }
401
402 subscrpt_write_debug_log( 'Auto-logged in newly created user ID: ' . $user_id . ' after checkout.' );
403 }
404
405 return $user_id;
406 }
407
408 /**
409 * Auto-create a guest account only while login is not enforced.
410 *
411 * @return bool
412 */
413 protected function should_auto_create_account() {
414 return self::is_guest_checkout_allowed() && ! self::is_guest_login_enforced();
415 }
416
417 /**
418 * Does the current cart contain a subscription product.
419 *
420 * @return bool
421 */
422 protected function cart_has_subscription() {
423 if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
424 return false;
425 }
426
427 return count( Helper::get_recurrs_from_cart( WC()->cart->get_cart() ) ) > 0;
428 }
429
430 /**
431 * Create the guest account before the order exists, for gateway customer binding.
432 *
433 * @param \WC_Customer $customer Customer object.
434 *
435 * @return void
436 */
437 public function maybe_create_user_from_customer( $customer ) {
438 if ( is_user_logged_in() || ! $this->should_auto_create_account() ) {
439 return;
440 }
441
442 // Already backed by a real user.
443 if ( $customer->get_id() ) {
444 return;
445 }
446
447 if ( ! $this->cart_has_subscription() ) {
448 return;
449 }
450
451 $billing_email = $customer->get_billing_email();
452
453 // Cannot create an account without an email.
454 if ( empty( $billing_email ) ) {
455 return;
456 }
457
458 // Never $customer->set_id() + save() here: it blanks user_email.
459 $this->maybe_create_user( $this->build_user_info( $customer ) );
460 }
461
462 /**
463 * Assign a user to a guest subscription order, creating one if needed.
464 *
465 * @param \WC_Order $order Order object.
466 *
467 * @return void
468 */
469 public function maybe_assign_user_to_order( $order ) {
470 if ( ! $this->should_auto_create_account() ) {
471 return;
472 }
473
474 if ( ! $order instanceof \WC_Order ) {
475 return;
476 }
477
478 // Don't proceed if the order already has a user.
479 if ( $order->get_customer_id() ) {
480 return;
481 }
482
483 if ( ! $this->cart_has_subscription() ) {
484 return;
485 }
486
487 // Reuse the user created earlier in this request when there is one.
488 $user_id = get_current_user_id();
489
490 if ( ! $user_id ) {
491 $user_info = $this->build_user_info( $order );
492
493 if ( empty( $user_info['billing_email'] ) ) {
494 return;
495 }
496
497 $user_id = $this->maybe_create_user( $user_info );
498 }
499
500 if ( ! $user_id ) {
501 return;
502 }
503
504 $order->set_customer_id( $user_id );
505
506 // The classic checkout hands over an unsaved order and persists it itself.
507 if ( $order->get_id() ) {
508 $order->save();
509 }
510 }
511 }
512