From 3dea6afaa05d2e6ad1d653d1a42b54fd8e0da1e5 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Tue, 12 Aug 2025 15:00:57 +0200 Subject: [PATCH 1/7] Allow minimal customer details on pay for order page --- includes/class-wc-stripe-customer.php | 39 +++++++++++-------- .../class-wc-stripe-intent-controller.php | 2 +- .../class-wc-stripe-upe-payment-gateway.php | 4 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index 4b5c244c4b..4da6d1df96 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -209,19 +209,19 @@ protected function generate_customer_request( $args = [] ) { /** * Validate that we have valid data before we try to create a customer. * - * @param array $create_customer_request - * @param bool $is_add_payment_method_page + * @param array $create_customer_request The base data to build the customer request. + * @param null|string $current_context Flag to indicate whether we are in a context where limited details are permitted. * * @throws WC_Stripe_Exception */ - private function validate_create_customer_request( $create_customer_request, $is_add_payment_method_page = false ) { + private function validate_create_customer_request( $create_customer_request, ?string $current_context = null ) { /** * Filters the required customer fields when creating a customer in Stripe. * * @since 9.7.0 - * @param array $required_fields The required customer fields as derived from the required billing fields in checkout. + * @param array $required_fields The required customer fields as derived from the required billing fields in checkout. In some contexts, like adding a payment method, we allow minimal details to be provided. */ - $required_fields = apply_filters( 'wc_stripe_create_customer_required_fields', $this->get_create_customer_required_fields( $is_add_payment_method_page ) ); + $required_fields = apply_filters( 'wc_stripe_create_customer_required_fields', $this->get_create_customer_required_fields( $current_context ) ); foreach ( $required_fields as $field => $field_requirements ) { if ( true === $field_requirements ) { @@ -258,13 +258,17 @@ private function validate_create_customer_request( $create_customer_request, $is /** * Get the list of required fields for the create customer request. * - * @param bool $is_add_payment_method_page + * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. * * @return array */ - private function get_create_customer_required_fields( $is_add_payment_method_page = false ) { - // If we are on the add payment method page, we need to check just for the email field. - if ( $is_add_payment_method_page ) { + private function get_create_customer_required_fields( ?string $current_context = null ) { + // If we are on the add payment method page or the pay for order page, we need to check just for the email field. + $contexts_with_minimal_details = [ + 'add_payment_method', + 'pay_for_order', + ]; + if ( in_array( $current_context, $contexts_with_minimal_details, true ) ) { return [ 'email' => true, ]; @@ -422,12 +426,12 @@ public function get_existing_customer( $email, $name ) { * Create a customer via API. * * @param array $args - * @param bool $is_add_payment_method_page Whether the request is for the add payment method page. + * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. * @return WP_Error|int * * @throws WC_Stripe_Exception */ - public function create_customer( $args = [], $is_add_payment_method_page = false ) { + public function create_customer( $args = [], $current_context = null ) { $args = $this->generate_customer_request( $args ); // For guest users, check if a customer already exists with the same email and name in Stripe account before creating a new one. @@ -445,7 +449,7 @@ public function create_customer( $args = [], $is_add_payment_method_page = false */ $create_customer_args = apply_filters( 'wc_stripe_create_customer_args', $args ); - $this->validate_create_customer_request( $create_customer_args, $is_add_payment_method_page ); + $this->validate_create_customer_request( $create_customer_args, $current_context ); $response = WC_Stripe_API::request( $create_customer_args, 'customers' ); } else { @@ -523,14 +527,15 @@ public function update_customer( $args = [], $is_retry = false ) { * Updates existing Stripe customer or creates new customer for User through API. * * @param array $args Additional arguments for the request (optional). + * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. * * @return string Customer ID * * @throws WC_Stripe_Exception */ - public function update_or_create_customer( $args = [], $is_add_payment_method_page = false ) { + public function update_or_create_customer( $args = [], $current_context = null ) { if ( empty( $this->get_id() ) ) { - return $this->recreate_customer( $args, $is_add_payment_method_page ); + return $this->recreate_customer( $args, $current_context ); } else { return $this->update_customer( $args ); } @@ -978,13 +983,13 @@ public function delete_id_from_meta() { * Recreates the customer for this user. * * @param array $args Additional arguments for the request (optional). - * @param bool $is_add_payment_method_page Whether the request is for the add payment method page. + * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. * * @return string ID of the new Customer object. */ - private function recreate_customer( $args = [], $is_add_payment_method_page = false ) { + private function recreate_customer( $args = [], $current_context = null ) { $this->delete_id_from_meta(); - return $this->create_customer( $args, $is_add_payment_method_page ); + return $this->create_customer( $args, $current_context ); } /** diff --git a/includes/class-wc-stripe-intent-controller.php b/includes/class-wc-stripe-intent-controller.php index e796d59641..60763c1171 100644 --- a/includes/class-wc-stripe-intent-controller.php +++ b/includes/class-wc-stripe-intent-controller.php @@ -1149,7 +1149,7 @@ public function create_and_confirm_setup_intent_ajax() { // Manually create the payment information array to create & confirm the setup intent. $payment_information = [ 'payment_method' => $payment_method, - 'customer' => $customer->update_or_create_customer( [], true ), + 'customer' => $customer->update_or_create_customer( [], 'add_payment_method' ), 'selected_payment_type' => $payment_type, 'return_url' => wc_get_account_endpoint_url( 'payment-methods' ), 'use_stripe_sdk' => 'true', // We want the user to complete the next steps via the JS elements. ref https://docs.stripe.com/api/setup_intents/create#create_setup_intent-use_stripe_sdk diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php index 11d0f2b29e..a7fd24e04e 100644 --- a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php +++ b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php @@ -2847,10 +2847,12 @@ private function get_customer_id_for_order( WC_Order $order ): string { $user = $this->get_user_from_order( $order ); $customer = new WC_Stripe_Customer( $user->ID ); + $current_context = $this->is_valid_pay_for_order_endpoint() ? 'pay_for_order' : null; + // Pass the order object so we can retrieve billing details // in payment flows where it is not present in the request. $args = [ 'order' => $order ]; - return $customer->update_or_create_customer( $args ); + return $customer->update_or_create_customer( $args, $current_context ); } /** From e1560a4c37aacca9ca7d99d09adadafb59d1ca0d Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Tue, 12 Aug 2025 15:02:13 +0200 Subject: [PATCH 2/7] Changelog --- changelog.txt | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index f4d09c1c31..017d64130b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -39,6 +39,7 @@ * Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support * Fix - Reduce number of calls to Stripe payment_methods API * Fix - Add `get_icon_url()` to Payment Method base class +* Fix - Relax customer validation that was preventing payments from the pay for order page" = 9.7.1 - 2025-07-28 = * Add - Add state mapping for Lithuania in express checkout diff --git a/readme.txt b/readme.txt index 4ff9a70767..88019a22a8 100644 --- a/readme.txt +++ b/readme.txt @@ -148,5 +148,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o * Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support * Fix - Reduce number of calls to Stripe payment_methods API * Fix - Add `get_icon_url()` to Payment Method base class +* Fix - Relax customer validation that was preventing payments from the pay for order page" [See changelog for full details across versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt). From 9686000a2d69e46b75e7d3952ffcf24c89523321 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Tue, 12 Aug 2025 16:00:39 +0200 Subject: [PATCH 3/7] Add normalization for existing boolean flag --- includes/class-wc-stripe-customer.php | 30 ++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index 4da6d1df96..b83bfca251 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -439,6 +439,9 @@ public function create_customer( $args = [], $current_context = null ) { $response = $this->get_existing_customer( $args['email'], $args['name'] ); } + // $current_context was initially introduced as a boolean flag, so check for old callers. + $current_context = $this->normalize_current_context( $current_context ); + if ( empty( $response ) ) { /** * Filters the arguments used to create a customer. @@ -535,12 +538,37 @@ public function update_customer( $args = [], $is_retry = false ) { */ public function update_or_create_customer( $args = [], $current_context = null ) { if ( empty( $this->get_id() ) ) { + // $current_context was initially introduced as a boolean flag, so check for old callers. + $current_context = $this->normalize_current_context( $current_context ); + return $this->recreate_customer( $args, $current_context ); } else { return $this->update_customer( $args ); } } + /** + * Normalize the current context to a string, as the argument was initially introduced as a boolean flag. + * + * @param string|bool|null $current_context The current context. + * @return string|null The normalized context. + */ + private function normalize_current_context( $current_context ): ?string { + if ( null === $current_context ) { + return null; + } + + if ( is_bool( $current_context ) ) { + return $current_context ? 'add_payment_method' : null; + } + + if ( is_string( $current_context ) ) { + return $current_context; + } + + return null; + } + /** * Checks to see if error is of invalid request * error and it is no such customer. @@ -987,7 +1015,7 @@ public function delete_id_from_meta() { * * @return string ID of the new Customer object. */ - private function recreate_customer( $args = [], $current_context = null ) { + private function recreate_customer( $args = [], ?string $current_context = null ) { $this->delete_id_from_meta(); return $this->create_customer( $args, $current_context ); } From 87c7e22fe2c271b1f9672e0f1af4d05060189a2e Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Tue, 12 Aug 2025 17:07:20 +0200 Subject: [PATCH 4/7] Remove trailing quotes from changelog --- changelog.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 017d64130b..70a5d656bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -39,7 +39,7 @@ * Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support * Fix - Reduce number of calls to Stripe payment_methods API * Fix - Add `get_icon_url()` to Payment Method base class -* Fix - Relax customer validation that was preventing payments from the pay for order page" +* Fix - Relax customer validation that was preventing payments from the pay for order page = 9.7.1 - 2025-07-28 = * Add - Add state mapping for Lithuania in express checkout diff --git a/readme.txt b/readme.txt index 88019a22a8..46ec090f4e 100644 --- a/readme.txt +++ b/readme.txt @@ -148,6 +148,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o * Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support * Fix - Reduce number of calls to Stripe payment_methods API * Fix - Add `get_icon_url()` to Payment Method base class -* Fix - Relax customer validation that was preventing payments from the pay for order page" +* Fix - Relax customer validation that was preventing payments from the pay for order page [See changelog for full details across versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt). From 3953d1a721d311b7e04033946230c9f6420d64c8 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Wed, 13 Aug 2025 11:53:30 +0200 Subject: [PATCH 5/7] Add constants for strings --- includes/class-wc-stripe-customer.php | 35 +++++++++++++------ .../class-wc-stripe-intent-controller.php | 2 +- .../class-wc-stripe-upe-payment-gateway.php | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index b83bfca251..432544ef9c 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -10,6 +10,24 @@ */ class WC_Stripe_Customer { + /** + * Constant for the customer context when adding a payment method. + */ + public const CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD = 'add_payment_method'; + + /** + * Constant for the customer context when paying for an order via the "Pay for Order" page.. + */ + public const CUSTOMER_CONTEXT_PAY_FOR_ORDER = 'pay_for_order'; + + /** + * Constants for the customer contexts where minimal billing details are permitted. + */ + protected const MINIMAL_BILLING_DETAILS_CONTEXTS = [ + self::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD, + self::CUSTOMER_CONTEXT_PAY_FOR_ORDER, + ]; + /** * String prefix for Stripe payment methods request transient. */ @@ -258,17 +276,12 @@ private function validate_create_customer_request( $create_customer_request, ?st /** * Get the list of required fields for the create customer request. * - * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. + * @param string|null $current_context The context we are creating the customer in. * * @return array */ private function get_create_customer_required_fields( ?string $current_context = null ) { - // If we are on the add payment method page or the pay for order page, we need to check just for the email field. - $contexts_with_minimal_details = [ - 'add_payment_method', - 'pay_for_order', - ]; - if ( in_array( $current_context, $contexts_with_minimal_details, true ) ) { + if ( in_array( $current_context, self::MINIMAL_BILLING_DETAILS_CONTEXTS, true ) ) { return [ 'email' => true, ]; @@ -426,7 +439,7 @@ public function get_existing_customer( $email, $name ) { * Create a customer via API. * * @param array $args - * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. + * @param string|null $current_context The context we are creating the customer in. * @return WP_Error|int * * @throws WC_Stripe_Exception @@ -530,7 +543,7 @@ public function update_customer( $args = [], $is_retry = false ) { * Updates existing Stripe customer or creates new customer for User through API. * * @param array $args Additional arguments for the request (optional). - * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. + * @param string|null $current_context The context we are creating the customer in. * * @return string Customer ID * @@ -559,7 +572,7 @@ private function normalize_current_context( $current_context ): ?string { } if ( is_bool( $current_context ) ) { - return $current_context ? 'add_payment_method' : null; + return $current_context ? self::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD : null; } if ( is_string( $current_context ) ) { @@ -1011,7 +1024,7 @@ public function delete_id_from_meta() { * Recreates the customer for this user. * * @param array $args Additional arguments for the request (optional). - * @param string|null $current_context The context we are creating the customer in. We specifically care about 'pay_for_order' and 'add_payment_method', where minimal details are available. + * @param string|null $current_context The context we are creating the customer in. * * @return string ID of the new Customer object. */ diff --git a/includes/class-wc-stripe-intent-controller.php b/includes/class-wc-stripe-intent-controller.php index d7fc51a915..eede946884 100644 --- a/includes/class-wc-stripe-intent-controller.php +++ b/includes/class-wc-stripe-intent-controller.php @@ -1197,7 +1197,7 @@ public function create_and_confirm_setup_intent_ajax() { // Manually create the payment information array to create & confirm the setup intent. $payment_information = [ 'payment_method' => $payment_method, - 'customer' => $customer->update_or_create_customer( [], 'add_payment_method' ), + 'customer' => $customer->update_or_create_customer( [], WC_Stripe_Customer::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD ), 'selected_payment_type' => $payment_type, 'return_url' => wc_get_account_endpoint_url( 'payment-methods' ), 'use_stripe_sdk' => 'true', // We want the user to complete the next steps via the JS elements. ref https://docs.stripe.com/api/setup_intents/create#create_setup_intent-use_stripe_sdk diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php index a6503f2b16..65bdc565f7 100644 --- a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php +++ b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php @@ -2858,7 +2858,7 @@ private function get_customer_id_for_order( WC_Order $order ): string { $user = $this->get_user_from_order( $order ); $customer = new WC_Stripe_Customer( $user->ID ); - $current_context = $this->is_valid_pay_for_order_endpoint() ? 'pay_for_order' : null; + $current_context = $this->is_valid_pay_for_order_endpoint() ? WC_Stripe_Customer::CUSTOMER_CONTEXT_PAY_FOR_ORDER : null; // Pass the order object so we can retrieve billing details // in payment flows where it is not present in the request. From 68549f2b8766b84990d556ceb0e6ffd511f2c985 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Wed, 13 Aug 2025 12:07:19 +0200 Subject: [PATCH 6/7] Make constant public; update unit tests --- includes/class-wc-stripe-customer.php | 2 +- tests/phpunit/WC_Stripe_Customer_Test.php | 54 ++++++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index 432544ef9c..5c79aac2de 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -23,7 +23,7 @@ class WC_Stripe_Customer { /** * Constants for the customer contexts where minimal billing details are permitted. */ - protected const MINIMAL_BILLING_DETAILS_CONTEXTS = [ + public const MINIMAL_BILLING_DETAILS_CONTEXTS = [ self::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD, self::CUSTOMER_CONTEXT_PAY_FOR_ORDER, ]; diff --git a/tests/phpunit/WC_Stripe_Customer_Test.php b/tests/phpunit/WC_Stripe_Customer_Test.php index 1215aca61f..422e06ab7a 100644 --- a/tests/phpunit/WC_Stripe_Customer_Test.php +++ b/tests/phpunit/WC_Stripe_Customer_Test.php @@ -98,21 +98,61 @@ public function provide_test_validate_create_customer_request_cases(): array { 'expected_exception_message' => 'missing_required_customer_field: address->country', 'expected_exception_string' => 'Missing required customer field: address->country', ], - 'add payment method page, all fields present and required, no overrides' => [ + 'add payment method page with boolean, all fields present and required, no overrides' => [ 'billing_fields' => [], // only email is required 'woo_billing_fields' => null, 'stripe_billing_fields' => null, 'expected_exception_message' => null, 'expected_exception_string' => null, - 'is_add_payment_method_page' => true, + 'current_context' => true, ], - 'add payment method page, email is empty string' => [ + 'add payment method page with context, all fields present and required, no overrides' => [ + 'billing_fields' => [], // only email is required + 'woo_billing_fields' => null, + 'stripe_billing_fields' => null, + 'expected_exception_message' => null, + 'expected_exception_string' => null, + 'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD, + ], + 'add payment method page with boolean, email is empty string' => [ + 'billing_fields' => [ 'email' => '' ], + 'woo_billing_fields' => null, + 'stripe_billing_fields' => null, + 'expected_exception_message' => 'missing_required_customer_field: email', + 'expected_exception_string' => 'Missing required customer field: email', + 'current_context' => true, + ], + 'add payment method page with context, email is empty string' => [ + 'billing_fields' => [ 'email' => '' ], + 'woo_billing_fields' => null, + 'stripe_billing_fields' => null, + 'expected_exception_message' => 'missing_required_customer_field: email', + 'expected_exception_string' => 'Missing required customer field: email', + 'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD, + ], + 'pay for order page, only email present and required, no overrides' => [ + 'billing_fields' => [], // only email is required + 'woo_billing_fields' => null, + 'stripe_billing_fields' => null, + 'expected_exception_message' => null, + 'expected_exception_string' => null, + 'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_PAY_FOR_ORDER, + ], + 'pay for order page, only email is empty string' => [ 'billing_fields' => [ 'email' => '' ], 'woo_billing_fields' => null, 'stripe_billing_fields' => null, 'expected_exception_message' => 'missing_required_customer_field: email', 'expected_exception_string' => 'Missing required customer field: email', - 'is_add_payment_method_page' => true, + 'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_PAY_FOR_ORDER, + ], + 'all fields present and required, no overrides, context is false' => [ + 'billing_fields' => [], + 'woo_billing_fields' => null, + 'stripe_billing_fields' => null, + 'expected_exception_message' => null, + 'expected_exception_string' => null, + 'current_context' => false, ], ]; } @@ -126,9 +166,9 @@ public function test_validate_create_customer_request( ?array $stripe_billing_fields = null, ?string $expected_exception_message = null, ?string $expected_exception_string = null, - ?bool $is_add_payment_method_page = false + $current_context = null ) { - if ( $is_add_payment_method_page ) { + if ( true === $current_context || in_array( $current_context, \WC_Stripe_Customer::MINIMAL_BILLING_DETAILS_CONTEXTS, true ) ) { $default_billing_data = [ 'email' => 'test@example.com', 'first_name' => '', @@ -278,7 +318,7 @@ public function test_validate_create_customer_request( } try { - $customer->create_customer( $args, $is_add_payment_method_page ); + $customer->create_customer( $args, $current_context ); } catch ( \WC_Stripe_Exception $stripe_exception ) { $was_exception_thrown = true; From 6628536490060d36b4a887e06e9f034ce3e34382 Mon Sep 17 00:00:00 2001 From: daledupreez Date: Thu, 14 Aug 2025 12:43:27 +0200 Subject: [PATCH 7/7] Remove double period Co-authored-by: Malith Senaweera <6216000+malithsen@users.noreply.github.com> --- includes/class-wc-stripe-customer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index 5c79aac2de..a67b3e3a04 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -16,7 +16,7 @@ class WC_Stripe_Customer { public const CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD = 'add_payment_method'; /** - * Constant for the customer context when paying for an order via the "Pay for Order" page.. + * Constant for the customer context when paying for an order via the "Pay for Order" page. */ public const CUSTOMER_CONTEXT_PAY_FOR_ORDER = 'pay_for_order';