diff --git a/changelog.txt b/changelog.txt index 1d3aeb624d..58169aab5d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ = 10.1.0 - xxxx-xx-xx = * Dev - Renames previous Order Helper class methods to use the `_id` suffix * Dev - Expands the Stripe Order Helper class to handle customer ID, card ID, UPE payment type, and UPE redirect status metas +* Fix - Use user, WooCommerce customer, and submitted fields to build Stripe customer data = 10.0.0 - 2025-10-14 = * Update - Removes frontend code related to Payment Request Buttons in the checkout page diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index ec5bab0d42..55f8b3eba4 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -134,12 +134,24 @@ public function set_user_id( $user_id ) { /** * Get user object. * - * @return WP_User + * @return WP_User|false */ protected function get_user() { return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false; } + /** + * Get the current WooCommerce customer object. + */ + protected function get_wc_customer(): ?WC_Customer { + $wc_customer = WC()->customer; + if ( $wc_customer instanceof WC_Customer ) { + return $wc_customer; + } + + return null; + } + /** * Store data from the Stripe API about this customer */ @@ -153,8 +165,19 @@ public function set_customer_data( $data ) { * @param array $args Additional arguments (optional). * @return array */ - protected function generate_customer_request( $args = [] ) { - $user = $this->get_user(); + public function generate_customer_request( $args = [] ) { + $user = $this->get_user(); + $wc_customer = $this->get_wc_customer(); + + if ( ! is_array( $args ) ) { + $args = []; + } + + $billing_first_name = ''; + $billing_last_name = ''; + $email = ''; + $username = ''; + if ( $user ) { $billing_first_name = get_user_meta( $user->ID, 'billing_first_name', true ); $billing_last_name = get_user_meta( $user->ID, 'billing_last_name', true ); @@ -169,50 +192,79 @@ protected function generate_customer_request( $args = [] ) { $billing_last_name = get_user_meta( $user->ID, 'last_name', true ); } - $email = $user->user_email; + $email = $user->user_email; + $username = $user->user_login; + } - // If the user email is not set, use the billing email. - if ( empty( $email ) ) { - $email = $this->get_billing_data_field( 'billing_email', $args ); + if ( $wc_customer ) { + if ( empty( $billing_first_name ) ) { + $billing_first_name = $wc_customer->get_first_name(); } - // translators: %1$s First name, %2$s Second name, %3$s Username. - $description = sprintf( __( 'Name: %1$s %2$s, Username: %3$s', 'woocommerce-gateway-stripe' ), $billing_first_name, $billing_last_name, $user->user_login ); + if ( empty( $billing_last_name ) ) { + $billing_last_name = $wc_customer->get_last_name(); + } - $defaults = [ - 'email' => $email, - 'description' => $description, - ]; + if ( empty( $email ) ) { + $email = $wc_customer->get_email(); + } - $billing_full_name = trim( $billing_first_name . ' ' . $billing_last_name ); - if ( ! empty( $billing_full_name ) ) { - $defaults['name'] = $billing_full_name; + if ( empty( $username ) ) { + $username = $wc_customer->get_username(); } - } else { - $billing_email = $this->get_billing_data_field( 'billing_email', $args ); + } + + // If the fields are not set yet, try getting the data directly from the incoming POST data or order context. + if ( empty( $email ) ) { + $email = $this->get_billing_data_field( 'billing_email', $args ); + } + + if ( empty( $billing_first_name ) ) { $billing_first_name = $this->get_billing_data_field( 'billing_first_name', $args ); - $billing_last_name = $this->get_billing_data_field( 'billing_last_name', $args ); + } + + if ( empty( $billing_last_name ) ) { + $billing_last_name = $this->get_billing_data_field( 'billing_last_name', $args ); + } + if ( empty( $username ) ) { // translators: %1$s First name, %2$s Second name. $description = sprintf( __( 'Name: %1$s %2$s, Guest', 'woocommerce-gateway-stripe' ), $billing_first_name, $billing_last_name ); + } else { + // translators: %1$s First name, %2$s Second name, %3$s Username. + $description = sprintf( __( 'Name: %1$s %2$s, Username: %3$s', 'woocommerce-gateway-stripe' ), $billing_first_name, $billing_last_name, $username ); + } - $defaults = [ - 'email' => $billing_email, - 'description' => $description, - ]; + $defaults = [ + 'email' => $email, + 'description' => $description, + ]; - $billing_full_name = trim( $billing_first_name . ' ' . $billing_last_name ); - if ( ! empty( $billing_full_name ) ) { - $defaults['name'] = $billing_full_name; - } + $billing_full_name = trim( $billing_first_name . ' ' . $billing_last_name ); + if ( ! empty( $billing_full_name ) ) { + $defaults['name'] = $billing_full_name; } $metadata = []; $defaults['metadata'] = apply_filters( 'wc_stripe_customer_metadata', $metadata, $user ); $defaults['preferred_locales'] = $this->get_customer_preferred_locale( $user ); - // Add customer address default values. - $address_fields = [ + $defaults['address'] = $this->generate_customer_address_fields( $args, $user, $wc_customer ); + + return wp_parse_args( $args, $defaults ); + } + + /** + * Helper function to build the address fields for the customer request. + * + * @param array $args Additional arguments. + * @param WP_User|false $user The user object or false. + * @param WC_Customer|null $wc_customer The WooCommerce customer object or null. + * + * @return array The address fields. + */ + private function generate_customer_address_fields( array $args, $user, ?WC_Customer $wc_customer ): array { + $address_field_map = [ 'line1' => 'billing_address_1', 'line2' => 'billing_address_2', 'postal_code' => 'billing_postcode', @@ -220,15 +272,31 @@ protected function generate_customer_request( $args = [] ) { 'state' => 'billing_state', 'country' => 'billing_country', ]; - foreach ( $address_fields as $key => $field ) { + + $address_fields = []; + + foreach ( $address_field_map as $key => $field ) { + $value = ''; + if ( $user ) { - $defaults['address'][ $key ] = get_user_meta( $user->ID, $field, true ); - } else { - $defaults['address'][ $key ] = $this->get_billing_data_field( $field, $args ); + $value = get_user_meta( $user->ID, $field, true ); + if ( null === $value ) { + $value = ''; + } } + + if ( '' === $value && $wc_customer && method_exists( $wc_customer, 'get_' . $field ) ) { + $value = $wc_customer->{ 'get_' . $field }(); + } + + if ( '' === $value ) { + $value = $this->get_billing_data_field( $field, $args ); + } + + $address_fields[ $key ] = $value; } - return wp_parse_args( $args, $defaults ); + return $address_fields; } /** @@ -343,7 +411,7 @@ function ( $field_data ) { * * @return string */ - private function get_billing_data_field( $field, $args = [] ) { + protected function get_billing_data_field( $field, $args = [] ) { $valid_fields = [ 'billing_email', 'billing_first_name', diff --git a/readme.txt b/readme.txt index 92b39ba858..c3568d38ab 100644 --- a/readme.txt +++ b/readme.txt @@ -113,5 +113,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o = 10.1.0 - xxxx-xx-xx = * Dev - Renames previous Order Helper class methods to use the `_id` suffix * Dev - Expands the Stripe Order Helper class to handle customer ID, card ID, UPE payment type, and UPE redirect status metas +* Fix - Use user, WooCommerce customer, and submitted fields to build Stripe customer data [See changelog for full details across versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt). diff --git a/tests/phpunit/WC_Stripe_Customer_Test.php b/tests/phpunit/WC_Stripe_Customer_Test.php index 422e06ab7a..83cdde48af 100644 --- a/tests/phpunit/WC_Stripe_Customer_Test.php +++ b/tests/phpunit/WC_Stripe_Customer_Test.php @@ -345,6 +345,8 @@ public function test_validate_create_customer_request( // Reset checkout fields, otherwise they persist across test cases. \WC_Checkout::instance()->checkout_fields = null; + // Reset WC Customer, otherwise it also persists across test cases. + \WC()->customer = null; } if ( null !== $expected_exception_message && ! $was_exception_thrown ) { @@ -355,4 +357,228 @@ public function test_validate_create_customer_request( $this->assertFalse( $was_exception_thrown, 'No exception was thrown when no exception was expected' ); } } + + public function provide_test_generate_customer_request_cases(): array { + return [ + 'all data present in expected user and user meta fields' => [ + 'user_data' => [ + 'ID' => 12345, + 'user_email' => 'test@example.com', + 'user_login' => 'testuser', + ], + 'user_meta_data' => [ + 'billing_first_name' => 'John', + 'billing_last_name' => 'Doe', + 'billing_address_1' => '123 Main St', + 'billing_address_2' => 'Apt 4B', + 'billing_city' => 'Anytown', + 'billing_state' => 'CA', + 'billing_postcode' => '98765', + 'billing_country' => 'US', + ], + 'wc_customer_data' => null, + 'billing_data' => null, + 'expected' => [ + 'email' => 'test@example.com', + 'description' => 'Name: John Doe, Username: testuser', + 'name' => 'John Doe', + 'metadata' => [], + 'preferred_locales' => [ 'en-US' ], + 'address' => [ + 'line1' => '123 Main St', + 'line2' => 'Apt 4B', + 'postal_code' => '98765', + 'city' => 'Anytown', + 'state' => 'CA', + 'country' => 'US', + ], + ], + ], + 'all data present in expected wc customer fields' => [ + 'user_data' => null, + 'user_meta_data' => null, + 'wc_customer_data' => [ + 'get_first_name' => 'John', + 'get_last_name' => 'Doe', + 'get_email' => 'test@example.com', + 'get_username' => 'testuser', + 'get_billing_address_1' => '123 Main St', + 'get_billing_address_2' => 'Apt 4B', + 'get_billing_city' => 'Anytown', + 'get_billing_state' => 'CA', + 'get_billing_postcode' => '98765', + 'get_billing_country' => 'US', + ], + 'billing_data' => null, + 'expected' => [ + 'email' => 'test@example.com', + 'description' => 'Name: John Doe, Username: testuser', + 'name' => 'John Doe', + 'metadata' => [], + 'preferred_locales' => [ 'en-US' ], + 'address' => [ + 'line1' => '123 Main St', + 'line2' => 'Apt 4B', + 'postal_code' => '98765', + 'city' => 'Anytown', + 'state' => 'CA', + 'country' => 'US', + ], + ], + ], + 'all data present in expected billing data fields' => [ + 'user_data' => null, + 'user_meta_data' => null, + 'wc_customer_data' => null, + 'billing_data' => [ + 'billing_email' => 'test@example.com', + 'billing_first_name' => 'John', + 'billing_last_name' => 'Doe', + 'billing_address_1' => '123 Main St', + 'billing_address_2' => 'Apt 4B', + 'billing_city' => 'Anytown', + 'billing_state' => 'CA', + 'billing_postcode' => '98765', + 'billing_country' => 'US', + ], + 'expected' => [ + 'email' => 'test@example.com', + 'description' => 'Name: John Doe, Guest', + 'name' => 'John Doe', + 'metadata' => [], + 'preferred_locales' => [ 'en-US' ], + 'address' => [ + 'line1' => '123 Main St', + 'line2' => 'Apt 4B', + 'postal_code' => '98765', + 'city' => 'Anytown', + 'state' => 'CA', + 'country' => 'US', + ], + ], + ], + 'data present across multiple sources' => [ + 'user_data' => [ + 'ID' => 12345, + 'user_email' => 'test@example.com', + ], + 'user_meta_data' => [ + 'billing_last_name' => 'Doe', + 'billing_address_1' => '321 Main St', + 'billing_postcode' => '98765', + ], + 'wc_customer_data' => [ + 'get_first_name' => 'Jane', + 'get_username' => 'testuser', + 'get_billing_address_2' => 'Apt 4B', + 'get_billing_city' => 'Anytown', + ], + 'billing_data' => [ + 'billing_state' => 'CA', + 'billing_country' => 'US', + ], + 'expected' => [ + 'email' => 'test@example.com', + 'description' => 'Name: Jane Doe, Username: testuser', + 'name' => 'Jane Doe', + 'metadata' => [], + 'preferred_locales' => [ 'en-US' ], + 'address' => [ + 'line1' => '321 Main St', + 'line2' => 'Apt 4B', + 'postal_code' => '98765', + 'city' => 'Anytown', + 'state' => 'CA', + 'country' => 'US', + ], + ], + ], + ]; + } + + /** + * @dataProvider provide_test_generate_customer_request_cases + */ + public function test_generate_customer_request( ?array $user_data, ?array $user_meta_data, ?array $wc_customer_data, ?array $billing_data, array $expected ) { + + $stripe_customer = $this->getMockBuilder( \WC_Stripe_Customer::class ) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get_user', + 'get_wc_customer', + 'get_billing_data_field', + ] + ) + ->getMock(); + + $user_return_value = false; + if ( is_array( $user_data ) ) { + if ( ! isset( $user_data['ID'] ) ) { + $user_data['ID'] = 12345; + } + $user_return_value = new \WP_User( (object) $user_data ); + + } + $stripe_customer->method( 'get_user' )->willReturn( $user_return_value ); + + $user_meta_filter = null; + if ( is_array( $user_meta_data ) ) { + $user_meta_filter = function ( $user_meta_value, $user_id, $meta_key, $single ) use ( $user_meta_data ) { + if ( $single && isset( $user_meta_data[ $meta_key ] ) ) { + return $user_meta_data[ $meta_key ]; + } + return $user_meta_value; + }; + add_filter( 'get_user_metadata', $user_meta_filter, 10, 4 ); + } + + $wc_customer_return_value = null; + if ( is_array( $wc_customer_data ) ) { + $mock_wc_customer = $this->getMockBuilder( \WC_Customer::class ) + ->disableOriginalConstructor() + ->onlyMethods( array_keys( $wc_customer_data ) ) + ->getMock(); + + foreach ( $wc_customer_data as $key => $value ) { + $mock_wc_customer->method( $key ) + ->willReturn( $value ); + } + + $wc_customer_return_value = $mock_wc_customer; + } + + $stripe_customer->method( 'get_wc_customer' ) + ->willReturn( $wc_customer_return_value ); + + $billing_data_callback = function ( $field, $args ) use ( $billing_data ) { + if ( is_array( $billing_data ) && isset( $billing_data[ $field ] ) ) { + return $billing_data[ $field ]; + } + return ''; + }; + + $stripe_customer->method( 'get_billing_data_field' ) + ->will( $this->returnCallback( $billing_data_callback ) ); + + $customer_request = $stripe_customer->generate_customer_request( [] ); + + if ( null !== $user_meta_filter ) { + remove_filter( 'get_user_metadata', $user_meta_filter, 10 ); + } + + $this->assertCount( count( $expected ), $customer_request ); + foreach ( $expected as $key => $value ) { + $this->assertArrayHasKey( $key, $customer_request ); + if ( is_array( $value ) ) { + $this->assertCount( count( $value ), $customer_request[ $key ] ); + foreach ( $value as $sub_key => $sub_value ) { + $this->assertArrayHasKey( $sub_key, $customer_request[ $key ] ); + $this->assertEquals( $sub_value, $customer_request[ $key ][ $sub_key ] ); + } + } else { + $this->assertEquals( $value, $customer_request[ $key ] ); + } + } + } }