| @@ -30,8 +30,13 @@ | ||
| 30 | 30 | |
| 31 | 31 | // Enforce Login/Registration in checkout |
| 32 | 32 | add_action( 'woocommerce_checkout_process', [ $this, 'require_account_creation' ] ); |
| 33 | 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 ); | |
| 34 | 39 | } |
| 35 | 40 | |
| 36 | 41 | /** |
| 37 | 42 | * Add Guest Checkout Settings Fields |
| @@ -43,9 +48,9 @@ | ||
| 43 | 48 | $guest_checkout_fields = [ |
| 44 | 49 | [ |
| 45 | 50 | 'type' => 'heading', |
| 46 | 51 | 'group' => 'guest_checkout', |
| 47 | - 'priority' => 1, | |
| 52 | + 'priority' => 2, | |
| 48 | 53 | 'field_data' => [ |
| 49 | 54 | 'title' => __( 'Guest Checkout', 'subscription' ), |
| 50 | 55 | 'description' => __( 'Manage guest checkout settings for subscriptions.', 'subscription' ), |
| 51 | 56 | ], |
| @@ -157,12 +162,12 @@ | ||
| 157 | 162 | $account_after_checkout = in_array( get_option( 'woocommerce_enable_delayed_account_creation' ), [ 1, '1', 'yes', 'on' ], true ); |
| 158 | 163 | |
| 159 | 164 | $issues = []; |
| 160 | 165 | if ( ! $guest_checkout_enabled ) { |
| 161 | - $issues[] = 'Guest checkout.'; | |
| 166 | + $issues[] = __( 'Guest checkout.', 'subscription' ); | |
| 162 | 167 | } |
| 163 | 168 | if ( ! $account_during_checkout ) { |
| 164 | - $issues[] = 'Account creation during checkout.'; | |
| 169 | + $issues[] = __( 'Account creation during checkout.', 'subscription' ); | |
| 165 | 170 | } |
| 166 | 171 | |
| 167 | 172 | if ( ! empty( $issues ) ) { |
| 168 | 173 | $settings_url = admin_url( 'admin.php?page=wc-settings&tab=account' ); |
| @@ -172,10 +177,13 @@ | ||
| 172 | 177 | $list_html .= '<li><span class="dashicons dashicons-arrow-right"></span> <strong>' . $issue . '</strong></li>'; |
| 173 | 178 | } |
| 174 | 179 | |
| 175 | 180 | $requirement_html = '<div class="notice notice-error is-dismissible">' . |
| 176 | - '<p>To ensure Subscriptions guest checkout functions correctly, please enable the following settings in WooCommerce. ' . | |
| 177 | - 'Click <a href="' . $settings_url . '">here</a> to go to the settings.</p>' . | |
| 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>' . | |
| 178 | 186 | '<ul>' . $list_html . '</ul></div>'; |
| 179 | 187 | |
| 180 | 188 | echo wp_kses_post( $requirement_html ); |
| 181 | 189 | } |
| @@ -183,10 +191,14 @@ | ||
| 183 | 191 | if ( $account_after_checkout ) { |
| 184 | 192 | $settings_url = admin_url( 'admin.php?page=wc-settings&tab=account' ); |
| 185 | 193 | |
| 186 | 194 | $requirement_html = '<div class="notice notice-warning is-dismissible">' . |
| 187 | - '<p>Enabling <strong>Account creation after checkout</strong> in WooCommerce settings may lead to issues with subscription orders for guest users.</p>' . | |
| 188 | - '<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>'; | |
| 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>'; | |
| 189 | 201 | |
| 190 | 202 | echo wp_kses_post( $requirement_html ); |
| 191 | 203 | } |
| 192 | 204 | } |
| @@ -277,7 +289,230 @@ | ||
| 277 | 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' ) |
| 278 | 290 | ), |
| 279 | 291 | 400 |
| 280 | 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(); | |
| 281 | 516 | } |
| 282 | 517 | } |
| 283 | 518 | } |