Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -5,6 +5,7 @@
* Update - Increases the default font size for the Optimized Checkout payment element to match the rest of the checkout form
* Fix - Checks for the subscription payment method (if it is Stripe) when verifying for the payment method detachment
* Dev - Implements WooCommerce constants for the tax statuses
* Fix - Relax customer validation that was preventing payments from the pay for order page

= 9.8.0 - 2025-08-11 =
* Add - Adds the current setting value for the Optimized Checkout to the Stripe System Status Report data
Expand Down
80 changes: 63 additions & 17 deletions includes/class-wc-stripe-customer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
public 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.
*/
Expand Down Expand Up @@ -209,19 +227,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 +276,12 @@ 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.
*
* @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 ( in_array( $current_context, self::MINIMAL_BILLING_DETAILS_CONTEXTS, true ) ) {
return [
'email' => true,
];
Expand Down Expand Up @@ -422,19 +439,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.
* @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 +465,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 +543,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.
*
* @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 ? self::CUSTOMER_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 +1024,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.
*
* @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 @@ -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( [], true ),
'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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2858,10 +2858,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() ? 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.
$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 @@ -115,5 +115,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
* Update - Increases the default font size for the Optimized Checkout payment element to match the rest of the checkout form
* Fix - Checks for the subscription payment method (if it is Stripe) when verifying for the payment method detachment
* Dev - Implements WooCommerce constants for the tax statuses
* 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).
54 changes: 47 additions & 7 deletions tests/phpunit/WC_Stripe_Customer_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
],
];
}
Expand All @@ -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' => '[email protected]',
'first_name' => '',
Expand Down Expand Up @@ -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;

Expand Down
Loading