Skip to content

Commit d43e1be

Browse files
Fix Add Payment Method page and required Stripe customer field validation (#4489)
* Require only email for Add Payment Method requests * Prevent logged out users from proceeding in Add Payment Method * Add unit tests * Use separate param * Add changelog and readme entries * Fix comment typo Co-authored-by: Malith Senaweera <[email protected]> * Fix unit tests * Remove error logs --------- Co-authored-by: Malith Senaweera <[email protected]>
1 parent 3c9680b commit d43e1be

File tree

5 files changed

+132
-69
lines changed

5 files changed

+132
-69
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* Fix - No such customer error when creating a payment method with a new Stripe account
1919
* Fix - Validate create customer payload against required billing fields before sending to Stripe
2020
* Update - Enhanced logging system with support for all log levels and improved context handling
21+
* Fix - Require email address only for Stripe customer validation when request is from the Add Payment Method page
2122
* Fix - Set default values for custom field options
2223
* Fix - Enforce rate limiter for failed add payment method attempts
2324
* Update - Add the number of pending webhooks to the Account status section

includes/class-wc-stripe-customer.php

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -203,55 +203,18 @@ protected function generate_customer_request( $args = [] ) {
203203
* Validate that we have valid data before we try to create a customer.
204204
*
205205
* @param array $create_customer_request
206+
* @param bool $is_add_payment_method_page
206207
*
207208
* @throws WC_Stripe_Exception
208209
*/
209-
private function validate_create_customer_request( $create_customer_request ) {
210-
$checkout_billing_fields = WC_Checkout::instance()->get_checkout_fields( 'billing' );
211-
$required_billing_fields = array_filter(
212-
$checkout_billing_fields,
213-
function ( $field_data ) {
214-
return $field_data['required'] ?? false;
215-
}
216-
);
217-
218-
$required_fields = [];
219-
220-
if ( isset( $required_billing_fields['billing_email'] ) ) {
221-
$required_fields['email'] = true;
222-
}
223-
224-
if ( isset( $required_billing_fields['billing_first_name'] ) || isset( $required_billing_fields['billing_last_name'] ) ) {
225-
$required_fields['name'] = true;
226-
}
227-
228-
$required_address_fields = [];
229-
$address_field_mapping = [
230-
'billing_address_1' => 'line1',
231-
'billing_address_2' => 'line2',
232-
'billing_city' => 'city',
233-
'billing_country' => 'country',
234-
'billing_postcode' => 'postal_code',
235-
'billing_state' => 'state',
236-
];
237-
238-
foreach ( $address_field_mapping as $field => $stripe_field_name ) {
239-
if ( isset( $required_billing_fields[ $field ] ) ) {
240-
$required_address_fields[ $stripe_field_name ] = true;
241-
}
242-
}
243-
244-
if ( [] !== $required_address_fields ) {
245-
$required_fields['address'] = $required_address_fields;
246-
}
247-
210+
private function validate_create_customer_request( $create_customer_request, $is_add_payment_method_page = false ) {
248211
/**
249212
* Filters the required customer fields when creating a customer in Stripe.
250213
*
251214
* @since 9.7.0
252215
* @param array $required_fields The required customer fields as derived from the required billing fields in checkout.
253216
*/
254-
$required_fields = apply_filters( 'wc_stripe_create_customer_required_fields', $required_fields );
217+
$required_fields = apply_filters( 'wc_stripe_create_customer_required_fields', $this->get_create_customer_required_fields( $is_add_payment_method_page ) );
255218

256219
foreach ( $required_fields as $field => $field_requirements ) {
257220
if ( true === $field_requirements ) {
@@ -285,6 +248,62 @@ function ( $field_data ) {
285248
}
286249
}
287250

251+
/**
252+
* Get the list of required fields for the create customer request.
253+
*
254+
* @param bool $is_add_payment_method_page
255+
*
256+
* @return array
257+
*/
258+
private function get_create_customer_required_fields( $is_add_payment_method_page = false ) {
259+
// If we are on the add payment method page, we need to check just for the email field.
260+
if ( $is_add_payment_method_page ) {
261+
return [
262+
'email' => true,
263+
];
264+
}
265+
266+
$checkout_billing_fields = WC_Checkout::instance()->get_checkout_fields( 'billing' );
267+
$required_billing_fields = array_filter(
268+
$checkout_billing_fields,
269+
function ( $field_data ) {
270+
return $field_data['required'] ?? false;
271+
}
272+
);
273+
274+
$required_fields = [];
275+
276+
if ( isset( $required_billing_fields['billing_email'] ) ) {
277+
$required_fields['email'] = true;
278+
}
279+
280+
if ( isset( $required_billing_fields['billing_first_name'] ) || isset( $required_billing_fields['billing_last_name'] ) ) {
281+
$required_fields['name'] = true;
282+
}
283+
284+
$required_address_fields = [];
285+
$address_field_mapping = [
286+
'billing_address_1' => 'line1',
287+
'billing_address_2' => 'line2',
288+
'billing_city' => 'city',
289+
'billing_country' => 'country',
290+
'billing_postcode' => 'postal_code',
291+
'billing_state' => 'state',
292+
];
293+
294+
foreach ( $address_field_mapping as $field => $stripe_field_name ) {
295+
if ( isset( $required_billing_fields[ $field ] ) ) {
296+
$required_address_fields[ $stripe_field_name ] = true;
297+
}
298+
}
299+
300+
if ( [] !== $required_address_fields ) {
301+
$required_fields['address'] = $required_address_fields;
302+
}
303+
304+
return $required_fields;
305+
}
306+
288307
/**
289308
* Get value of billing data field, either from POST or order object.
290309
*
@@ -396,11 +415,12 @@ public function get_existing_customer( $email, $name ) {
396415
* Create a customer via API.
397416
*
398417
* @param array $args
418+
* @param bool $is_add_payment_method_page Whether the request is for the add payment method page.
399419
* @return WP_Error|int
400420
*
401421
* @throws WC_Stripe_Exception
402422
*/
403-
public function create_customer( $args = [] ) {
423+
public function create_customer( $args = [], $is_add_payment_method_page = false ) {
404424
$args = $this->generate_customer_request( $args );
405425

406426
// For guest users, check if a customer already exists with the same email and name in Stripe account before creating a new one.
@@ -418,15 +438,15 @@ public function create_customer( $args = [] ) {
418438
*/
419439
$create_customer_args = apply_filters( 'wc_stripe_create_customer_args', $args );
420440

421-
$this->validate_create_customer_request( $create_customer_args );
441+
$this->validate_create_customer_request( $create_customer_args, $is_add_payment_method_page );
422442

423443
$response = WC_Stripe_API::request( $create_customer_args, 'customers' );
424444
} else {
425445
/**
426446
* This filter is documented in includes/class-wc-stripe-customer.php.
427447
*/
428448
$update_customer_args = apply_filters( 'wc_stripe_update_customer_args', $args );
429-
$response = WC_Stripe_API::request( $update_customer_args, 'customers/' . $response->id );
449+
$response = WC_Stripe_API::request( $update_customer_args, 'customers/' . $response->id );
430450
}
431451

432452
if ( ! empty( $response->error ) ) {
@@ -501,9 +521,9 @@ public function update_customer( $args = [], $is_retry = false ) {
501521
*
502522
* @throws WC_Stripe_Exception
503523
*/
504-
public function update_or_create_customer( $args = [] ) {
524+
public function update_or_create_customer( $args = [], $is_add_payment_method_page = false ) {
505525
if ( empty( $this->get_id() ) ) {
506-
return $this->recreate_customer( $args );
526+
return $this->recreate_customer( $args, $is_add_payment_method_page );
507527
} else {
508528
return $this->update_customer( $args );
509529
}
@@ -858,12 +878,13 @@ public function delete_id_from_meta() {
858878
* Recreates the customer for this user.
859879
*
860880
* @param array $args Additional arguments for the request (optional).
881+
* @param bool $is_add_payment_method_page Whether the request is for the add payment method page.
861882
*
862883
* @return string ID of the new Customer object.
863884
*/
864-
private function recreate_customer( $args = [] ) {
885+
private function recreate_customer( $args = [], $is_add_payment_method_page = false ) {
865886
$this->delete_id_from_meta();
866-
return $this->create_customer( $args );
887+
return $this->create_customer( $args, $is_add_payment_method_page );
867888
}
868889

869890
/**

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,13 +1138,17 @@ public function create_and_confirm_setup_intent_ajax() {
11381138
}
11391139

11401140
// Determine the customer managing the payment methods, create one if we don't have one already.
1141-
$user = wp_get_current_user();
1141+
$user = wp_get_current_user();
1142+
// This page is only accessible to logged in users.
1143+
if ( ! $user->ID ) {
1144+
throw new WC_Stripe_Exception( 'User not found.', __( "We're not able to add this payment method. Please refresh the page and try again.", 'woocommerce-gateway-stripe' ) );
1145+
}
11421146
$customer = new WC_Stripe_Customer( $user->ID );
11431147

11441148
// Manually create the payment information array to create & confirm the setup intent.
11451149
$payment_information = [
11461150
'payment_method' => $payment_method,
1147-
'customer' => $customer->update_or_create_customer(),
1151+
'customer' => $customer->update_or_create_customer( [], true ),
11481152
'selected_payment_type' => $payment_type,
11491153
'return_url' => wc_get_account_endpoint_url( 'payment-methods' ),
11501154
'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

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
128128
* Fix - No such customer error when creating a payment method with a new Stripe account
129129
* Fix - Validate create customer payload against required billing fields before sending to Stripe
130130
* Update - Enhanced logging system with support for all log levels and improved context handling
131+
* Fix - Require email address only for Stripe customer validation when request is from the Add Payment Method page
131132
* Fix - Set default values for custom field options
132133
* Fix - Enforce rate limiter for failed add payment method attempts
133134
* Update - Add the number of pending webhooks to the Account status section

tests/phpunit/WC_Stripe_Customer_Test.php

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,38 +46,38 @@ public function provide_test_validate_create_customer_request_cases(): array {
4646
'expected_exception_message' => null,
4747
'expected_exception_string' => null,
4848
],
49-
'email is empty string' => [
49+
'email is empty string' => [
5050
'billing_fields' => [ 'email' => '' ],
5151
'woo_billing_fields' => null,
5252
'stripe_billing_fields' => null,
5353
'expected_exception_message' => 'missing_required_customer_field: email',
5454
'expected_exception_string' => 'Missing required customer field: email',
5555
],
56-
'email is whitespace string' => [
56+
'email is whitespace string' => [
5757
'billing_fields' => [ 'email' => ' ' ],
5858
'woo_billing_fields' => null,
5959
'stripe_billing_fields' => null,
6060
'expected_exception_message' => 'missing_required_customer_field: email',
6161
'expected_exception_string' => 'Missing required customer field: email',
6262
],
63-
'name is null' => [
63+
'name is null' => [
6464
'billing_fields' => [
6565
'first_name' => null,
66-
'last_name' => '',
66+
'last_name' => '',
6767
],
6868
'woo_billing_fields' => null,
6969
'stripe_billing_fields' => null,
7070
'expected_exception_message' => 'missing_required_customer_field: name',
7171
'expected_exception_string' => 'Missing required customer field: name',
7272
],
73-
'address line1 is empty string' => [
73+
'address line1 is empty string' => [
7474
'billing_fields' => [ 'address_1' => '' ],
7575
'woo_billing_fields' => null,
7676
'stripe_billing_fields' => null,
7777
'expected_exception_message' => 'missing_required_customer_field: address->line1',
7878
'expected_exception_string' => 'Missing required customer field: address->line1',
7979
],
80-
'address city is empty string' => [
80+
'address city is empty string' => [
8181
'billing_fields' => [ 'city' => '' ],
8282
'woo_billing_fields' => null,
8383
'stripe_billing_fields' => null,
@@ -91,30 +91,66 @@ public function provide_test_validate_create_customer_request_cases(): array {
9191
'expected_exception_message' => 'missing_required_customer_field: address->city',
9292
'expected_exception_string' => 'Missing required customer field: address->city',
9393
],
94-
'address country is empty string' => [
94+
'address country is empty string' => [
9595
'billing_fields' => [ 'country' => '' ],
9696
'woo_billing_fields' => null,
9797
'stripe_billing_fields' => null,
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' => [
102+
'billing_fields' => [], // only email is required
103+
'woo_billing_fields' => null,
104+
'stripe_billing_fields' => null,
105+
'expected_exception_message' => null,
106+
'expected_exception_string' => null,
107+
'is_add_payment_method_page' => true,
108+
],
109+
'add payment method page, email is empty string' => [
110+
'billing_fields' => [ 'email' => '' ],
111+
'woo_billing_fields' => null,
112+
'stripe_billing_fields' => null,
113+
'expected_exception_message' => 'missing_required_customer_field: email',
114+
'expected_exception_string' => 'Missing required customer field: email',
115+
'is_add_payment_method_page' => true,
116+
],
101117
];
102118
}
103119

104120
/**
105121
* @dataProvider provide_test_validate_create_customer_request_cases
106122
*/
107-
public function test_validate_create_customer_request( array $billing_fields = [], ?array $woo_billing_fields = null, ?array $stripe_billing_fields = null, ?string $expected_exception_message = null, ?string $expected_exception_string = null ) {
108-
$default_billing_data = [
109-
'email' => '[email protected]',
110-
'first_name' => 'John',
111-
'last_name' => 'Doe',
112-
'address_1' => '123 Main St',
113-
'city' => 'Anytown',
114-
'state' => 'CA',
115-
'postcode' => '12345',
116-
'country' => 'US',
117-
];
123+
public function test_validate_create_customer_request(
124+
array $billing_fields = [],
125+
?array $woo_billing_fields = null,
126+
?array $stripe_billing_fields = null,
127+
?string $expected_exception_message = null,
128+
?string $expected_exception_string = null,
129+
?bool $is_add_payment_method_page = false
130+
) {
131+
if ( $is_add_payment_method_page ) {
132+
$default_billing_data = [
133+
'email' => '[email protected]',
134+
'first_name' => '',
135+
'last_name' => '',
136+
'address_1' => '',
137+
'city' => '',
138+
'state' => '',
139+
'postcode' => '',
140+
'country' => '',
141+
];
142+
} else {
143+
$default_billing_data = [
144+
'email' => '[email protected]',
145+
'first_name' => 'John',
146+
'last_name' => 'Doe',
147+
'address_1' => '123 Main St',
148+
'city' => 'Anytown',
149+
'state' => 'CA',
150+
'postcode' => '12345',
151+
'country' => 'US',
152+
];
153+
}
118154

119155
$billing_data = wp_parse_args( $billing_fields, $default_billing_data );
120156

@@ -193,7 +229,7 @@ public function test_validate_create_customer_request( array $billing_fields = [
193229
$mock_order->method( 'get_billing_postcode' )->willReturn( $billing_data['postcode'] );
194230
$mock_order->method( 'get_billing_state' )->willReturn( $billing_data['state'] );
195231

196-
$args = [
232+
$args = [
197233
'order' => $mock_order,
198234
];
199235
$customer = new \WC_Stripe_Customer();
@@ -242,7 +278,7 @@ public function test_validate_create_customer_request( array $billing_fields = [
242278
}
243279

244280
try {
245-
$customer->create_customer( $args );
281+
$customer->create_customer( $args, $is_add_payment_method_page );
246282
} catch ( \WC_Stripe_Exception $stripe_exception ) {
247283
$was_exception_thrown = true;
248284

0 commit comments

Comments
 (0)