Skip to content

Commit d087de6

Browse files
daledupreezdiegocurbelo
authored andcommitted
Fix address validation for manually entered orders (#4576)
* Allow minimal customer details on pay for order page * Changelog * Add normalization for existing boolean flag * Remove trailing quotes from changelog * Add constants for strings * Make constant public; update unit tests * Remove double period
1 parent 26b1cdf commit d087de6

File tree

6 files changed

+118
-27
lines changed

6 files changed

+118
-27
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
= 9.8.1 - xxxx-xx-xx =
44
* Fix - Remove connection type requirement from PMC sync migration attempt
5+
* Fix - Relax customer validation that was preventing payments from the pay for order page
56

67
= 9.8.0 - 2025-08-11 =
78
* Add - Adds the current setting value for the Optimized Checkout to the Stripe System Status Report data

includes/class-wc-stripe-customer.php

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@
1010
*/
1111
class WC_Stripe_Customer {
1212

13+
/**
14+
* Constant for the customer context when adding a payment method.
15+
*/
16+
public const CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD = 'add_payment_method';
17+
18+
/**
19+
* Constant for the customer context when paying for an order via the "Pay for Order" page.
20+
*/
21+
public const CUSTOMER_CONTEXT_PAY_FOR_ORDER = 'pay_for_order';
22+
23+
/**
24+
* Constants for the customer contexts where minimal billing details are permitted.
25+
*/
26+
public const MINIMAL_BILLING_DETAILS_CONTEXTS = [
27+
self::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD,
28+
self::CUSTOMER_CONTEXT_PAY_FOR_ORDER,
29+
];
30+
1331
/**
1432
* String prefix for Stripe payment methods request transient.
1533
*/
@@ -209,19 +227,19 @@ protected function generate_customer_request( $args = [] ) {
209227
/**
210228
* Validate that we have valid data before we try to create a customer.
211229
*
212-
* @param array $create_customer_request
213-
* @param bool $is_add_payment_method_page
230+
* @param array $create_customer_request The base data to build the customer request.
231+
* @param null|string $current_context Flag to indicate whether we are in a context where limited details are permitted.
214232
*
215233
* @throws WC_Stripe_Exception
216234
*/
217-
private function validate_create_customer_request( $create_customer_request, $is_add_payment_method_page = false ) {
235+
private function validate_create_customer_request( $create_customer_request, ?string $current_context = null ) {
218236
/**
219237
* Filters the required customer fields when creating a customer in Stripe.
220238
*
221239
* @since 9.7.0
222-
* @param array $required_fields The required customer fields as derived from the required billing fields in checkout.
240+
* @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.
223241
*/
224-
$required_fields = apply_filters( 'wc_stripe_create_customer_required_fields', $this->get_create_customer_required_fields( $is_add_payment_method_page ) );
242+
$required_fields = apply_filters( 'wc_stripe_create_customer_required_fields', $this->get_create_customer_required_fields( $current_context ) );
225243

226244
foreach ( $required_fields as $field => $field_requirements ) {
227245
if ( true === $field_requirements ) {
@@ -258,13 +276,12 @@ private function validate_create_customer_request( $create_customer_request, $is
258276
/**
259277
* Get the list of required fields for the create customer request.
260278
*
261-
* @param bool $is_add_payment_method_page
279+
* @param string|null $current_context The context we are creating the customer in.
262280
*
263281
* @return array
264282
*/
265-
private function get_create_customer_required_fields( $is_add_payment_method_page = false ) {
266-
// If we are on the add payment method page, we need to check just for the email field.
267-
if ( $is_add_payment_method_page ) {
283+
private function get_create_customer_required_fields( ?string $current_context = null ) {
284+
if ( in_array( $current_context, self::MINIMAL_BILLING_DETAILS_CONTEXTS, true ) ) {
268285
return [
269286
'email' => true,
270287
];
@@ -422,19 +439,22 @@ public function get_existing_customer( $email, $name ) {
422439
* Create a customer via API.
423440
*
424441
* @param array $args
425-
* @param bool $is_add_payment_method_page Whether the request is for the add payment method page.
442+
* @param string|null $current_context The context we are creating the customer in.
426443
* @return WP_Error|int
427444
*
428445
* @throws WC_Stripe_Exception
429446
*/
430-
public function create_customer( $args = [], $is_add_payment_method_page = false ) {
447+
public function create_customer( $args = [], $current_context = null ) {
431448
$args = $this->generate_customer_request( $args );
432449

433450
// For guest users, check if a customer already exists with the same email and name in Stripe account before creating a new one.
434451
if ( ! $this->get_id() && 0 === $this->get_user_id() && ! empty( $args['email'] ) && ! empty( $args['name'] ) ) {
435452
$response = $this->get_existing_customer( $args['email'], $args['name'] );
436453
}
437454

455+
// $current_context was initially introduced as a boolean flag, so check for old callers.
456+
$current_context = $this->normalize_current_context( $current_context );
457+
438458
if ( empty( $response ) ) {
439459
/**
440460
* Filters the arguments used to create a customer.
@@ -445,7 +465,7 @@ public function create_customer( $args = [], $is_add_payment_method_page = false
445465
*/
446466
$create_customer_args = apply_filters( 'wc_stripe_create_customer_args', $args );
447467

448-
$this->validate_create_customer_request( $create_customer_args, $is_add_payment_method_page );
468+
$this->validate_create_customer_request( $create_customer_args, $current_context );
449469

450470
$response = WC_Stripe_API::request( $create_customer_args, 'customers' );
451471
} else {
@@ -523,19 +543,45 @@ public function update_customer( $args = [], $is_retry = false ) {
523543
* Updates existing Stripe customer or creates new customer for User through API.
524544
*
525545
* @param array $args Additional arguments for the request (optional).
546+
* @param string|null $current_context The context we are creating the customer in.
526547
*
527548
* @return string Customer ID
528549
*
529550
* @throws WC_Stripe_Exception
530551
*/
531-
public function update_or_create_customer( $args = [], $is_add_payment_method_page = false ) {
552+
public function update_or_create_customer( $args = [], $current_context = null ) {
532553
if ( empty( $this->get_id() ) ) {
533-
return $this->recreate_customer( $args, $is_add_payment_method_page );
554+
// $current_context was initially introduced as a boolean flag, so check for old callers.
555+
$current_context = $this->normalize_current_context( $current_context );
556+
557+
return $this->recreate_customer( $args, $current_context );
534558
} else {
535559
return $this->update_customer( $args );
536560
}
537561
}
538562

563+
/**
564+
* Normalize the current context to a string, as the argument was initially introduced as a boolean flag.
565+
*
566+
* @param string|bool|null $current_context The current context.
567+
* @return string|null The normalized context.
568+
*/
569+
private function normalize_current_context( $current_context ): ?string {
570+
if ( null === $current_context ) {
571+
return null;
572+
}
573+
574+
if ( is_bool( $current_context ) ) {
575+
return $current_context ? self::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD : null;
576+
}
577+
578+
if ( is_string( $current_context ) ) {
579+
return $current_context;
580+
}
581+
582+
return null;
583+
}
584+
539585
/**
540586
* Checks to see if error is of invalid request
541587
* error and it is no such customer.
@@ -978,13 +1024,13 @@ public function delete_id_from_meta() {
9781024
* Recreates the customer for this user.
9791025
*
9801026
* @param array $args Additional arguments for the request (optional).
981-
* @param bool $is_add_payment_method_page Whether the request is for the add payment method page.
1027+
* @param string|null $current_context The context we are creating the customer in.
9821028
*
9831029
* @return string ID of the new Customer object.
9841030
*/
985-
private function recreate_customer( $args = [], $is_add_payment_method_page = false ) {
1031+
private function recreate_customer( $args = [], ?string $current_context = null ) {
9861032
$this->delete_id_from_meta();
987-
return $this->create_customer( $args, $is_add_payment_method_page );
1033+
return $this->create_customer( $args, $current_context );
9881034
}
9891035

9901036
/**

includes/class-wc-stripe-intent-controller.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ public function create_and_confirm_setup_intent_ajax() {
11971197
// Manually create the payment information array to create & confirm the setup intent.
11981198
$payment_information = [
11991199
'payment_method' => $payment_method,
1200-
'customer' => $customer->update_or_create_customer( [], true ),
1200+
'customer' => $customer->update_or_create_customer( [], WC_Stripe_Customer::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD ),
12011201
'selected_payment_type' => $payment_type,
12021202
'return_url' => wc_get_account_endpoint_url( 'payment-methods' ),
12031203
'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

includes/payment-methods/class-wc-stripe-upe-payment-gateway.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2858,10 +2858,12 @@ private function get_customer_id_for_order( WC_Order $order ): string {
28582858
$user = $this->get_user_from_order( $order );
28592859
$customer = new WC_Stripe_Customer( $user->ID );
28602860

2861+
$current_context = $this->is_valid_pay_for_order_endpoint() ? WC_Stripe_Customer::CUSTOMER_CONTEXT_PAY_FOR_ORDER : null;
2862+
28612863
// Pass the order object so we can retrieve billing details
28622864
// in payment flows where it is not present in the request.
28632865
$args = [ 'order' => $order ];
2864-
return $customer->update_or_create_customer( $args );
2866+
return $customer->update_or_create_customer( $args, $current_context );
28652867
}
28662868

28672869
/**

readme.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
112112

113113
= 9.8.1 - xxxx-xx-xx =
114114

115-
* Fix - Remove connection type requirement from PMC sync migration attempt
115+
**Important Fixes and Updates**
116116

117+
* Fix - Remove connection type requirement from PMC sync migration attempt
118+
* Fix - Relax customer validation that was preventing payments from the pay for order page
117119

118120
= 9.8.0 - 2025-08-11 =
119121

tests/phpunit/WC_Stripe_Customer_Test.php

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,61 @@ public function provide_test_validate_create_customer_request_cases(): array {
9898
'expected_exception_message' => 'missing_required_customer_field: address->country',
9999
'expected_exception_string' => 'Missing required customer field: address->country',
100100
],
101-
'add payment method page, all fields present and required, no overrides' => [
101+
'add payment method page with boolean, all fields present and required, no overrides' => [
102102
'billing_fields' => [], // only email is required
103103
'woo_billing_fields' => null,
104104
'stripe_billing_fields' => null,
105105
'expected_exception_message' => null,
106106
'expected_exception_string' => null,
107-
'is_add_payment_method_page' => true,
107+
'current_context' => true,
108108
],
109-
'add payment method page, email is empty string' => [
109+
'add payment method page with context, all fields present and required, no overrides' => [
110+
'billing_fields' => [], // only email is required
111+
'woo_billing_fields' => null,
112+
'stripe_billing_fields' => null,
113+
'expected_exception_message' => null,
114+
'expected_exception_string' => null,
115+
'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD,
116+
],
117+
'add payment method page with boolean, email is empty string' => [
118+
'billing_fields' => [ 'email' => '' ],
119+
'woo_billing_fields' => null,
120+
'stripe_billing_fields' => null,
121+
'expected_exception_message' => 'missing_required_customer_field: email',
122+
'expected_exception_string' => 'Missing required customer field: email',
123+
'current_context' => true,
124+
],
125+
'add payment method page with context, email is empty string' => [
126+
'billing_fields' => [ 'email' => '' ],
127+
'woo_billing_fields' => null,
128+
'stripe_billing_fields' => null,
129+
'expected_exception_message' => 'missing_required_customer_field: email',
130+
'expected_exception_string' => 'Missing required customer field: email',
131+
'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_ADD_PAYMENT_METHOD,
132+
],
133+
'pay for order page, only email present and required, no overrides' => [
134+
'billing_fields' => [], // only email is required
135+
'woo_billing_fields' => null,
136+
'stripe_billing_fields' => null,
137+
'expected_exception_message' => null,
138+
'expected_exception_string' => null,
139+
'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_PAY_FOR_ORDER,
140+
],
141+
'pay for order page, only email is empty string' => [
110142
'billing_fields' => [ 'email' => '' ],
111143
'woo_billing_fields' => null,
112144
'stripe_billing_fields' => null,
113145
'expected_exception_message' => 'missing_required_customer_field: email',
114146
'expected_exception_string' => 'Missing required customer field: email',
115-
'is_add_payment_method_page' => true,
147+
'current_context' => \WC_Stripe_Customer::CUSTOMER_CONTEXT_PAY_FOR_ORDER,
148+
],
149+
'all fields present and required, no overrides, context is false' => [
150+
'billing_fields' => [],
151+
'woo_billing_fields' => null,
152+
'stripe_billing_fields' => null,
153+
'expected_exception_message' => null,
154+
'expected_exception_string' => null,
155+
'current_context' => false,
116156
],
117157
];
118158
}
@@ -126,9 +166,9 @@ public function test_validate_create_customer_request(
126166
?array $stripe_billing_fields = null,
127167
?string $expected_exception_message = null,
128168
?string $expected_exception_string = null,
129-
?bool $is_add_payment_method_page = false
169+
$current_context = null
130170
) {
131-
if ( $is_add_payment_method_page ) {
171+
if ( true === $current_context || in_array( $current_context, \WC_Stripe_Customer::MINIMAL_BILLING_DETAILS_CONTEXTS, true ) ) {
132172
$default_billing_data = [
133173
'email' => '[email protected]',
134174
'first_name' => '',
@@ -278,7 +318,7 @@ public function test_validate_create_customer_request(
278318
}
279319

280320
try {
281-
$customer->create_customer( $args, $is_add_payment_method_page );
321+
$customer->create_customer( $args, $current_context );
282322
} catch ( \WC_Stripe_Exception $stripe_exception ) {
283323
$was_exception_thrown = true;
284324

0 commit comments

Comments
 (0)