Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 50 additions & 17 deletions includes/class-wc-stripe-customer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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,
];
Expand Down Expand Up @@ -422,19 +426,22 @@ 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.
if ( ! $this->get_id() && 0 === $this->get_user_id() && ! empty( $args['email'] ) && ! empty( $args['name'] ) ) {
$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.
Expand All @@ -445,7 +452,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 {
Expand Down Expand Up @@ -523,19 +530,45 @@ 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 );
// $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.
Expand Down Expand Up @@ -978,13 +1011,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 = [], ?string $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 );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion includes/class-wc-stripe-intent-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

/**
Expand Down
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Loading