Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"New Checkout" requires Country input? #3558

Open
giilby opened this issue Oct 29, 2024 · 5 comments
Open

"New Checkout" requires Country input? #3558

giilby opened this issue Oct 29, 2024 · 5 comments

Comments

@giilby
Copy link

giilby commented Oct 29, 2024

Describe the bug
For years, "Country" has been an optional field in our checkout form, and this has worked just fine with the legacy Stripe checkout experience. However, after enabling "New Checkout," customers see this error message (which comes directly from Stripe) if they do not select a Country:

"You passed an empty string for 'billing_details[address][country]'. We assume empty values are an attempt to unset a parameter; however 'billing_details[address][country]' cannot be unset. You should remove 'billing_details[address][country]' from your request or supply a non-empty value."

I tried to make "Country" a required field, but couldn't actually figure out how to do that (granted, it might be an issue with our theme...I'll keep digging). But, in any case, I see two issues here:

  1. The plugin probably shouldn't display raw error messages directly from Stripe, right? I think this message is too technical to be user-facing.
  2. After looking at Stripe's docs, I didn't see any evidence that "Country" is required by the /v1/payment_methods API. Would this issue be fixed by omitting the country field from billing_details.address when it's not specified?

To Reproduce

  1. Set General -> Shipping location(s) to "Disable shipping & shipping calculations"
  2. Set General -> Default customer location to "No location by default"
  3. Configure your theme to make "Country" optional during checkout
  4. Attempt to check out

Expected behavior
Checkout succeeds even if Country is not specified. NOTE: if there's some reason that Country should actually be required (e.g. because it helps Stripe better protect against fraud), I'm happy to hear that!

Environment (please complete the following information):

  • WordPress Version: 6.6.2
  • WooCommerce Version: 9.3.2
  • Stripe Plugin Version: 8.8.1
@james-allan
Copy link
Contributor

Hi @giilby, thanks for filing the issue.

The plugin probably shouldn't display raw error messages directly from Stripe, right? I think this message is too technical to be user-facing.

Yep, I agree. In most cases, the error messages Stripe respond with are localized and are useful to display directly to the customer. This is not one of them. 😅 I'll file a separate issue for this. Unfortunately this may mean we show a more a more generic message, but we should be able to include the raw message in the browser console if folks want to go digging for more technical details.


After looking at Stripe's docs, I didn't see any evidence that "Country" is required by the /v1/payment_methods API. Would this issue be fixed by omitting the country field from billing_details.address when it's not specified?

So I looked into this. Stripe's Docs are a bit vague when it comes to what is required or not. This is a direct link to the documentation for the Stripe API function we're using on the frontend to create the payment method object. There it says this about the billing_details:

Billing information associated with the PaymentMethod that may be used or required by particular types of payment methods.
Stripe @ https://docs.stripe.com/js/payment_methods/create_payment_method

So they leave the door open by saying may be required and it's depends on the payment method type (eg card vs Cash App Pay).

Following your suggestion I took a look into whether we could just omit the country field all together (rather than pass an empty string). In that case, the payment failed with this error:

IntegrationError: You specified "never" for fields.billing_details.address.country when creating the payment Element, but did not pass params.billing_details.address.country when calling stripe.createPaymentMethod(). If you opt out of collecting data via the payment Element using the fields option, the data must be passed in when calling stripe.createPaymentMethod().

Essentially this error is saying:

  1. When we (the WooCommerce Stripe plugin) created the Stripe Payment Elements we told Stripe not to collect Billing details. This is because we would collect them in our checkout fields and pass them to Stripe.
  2. If we tell Stripe not to collect these fields, then we should pass them.

I took this a step further and updated the code which tells Stripe to "never" collect the billing country and this is what I saw on the checkout.

Image
Stripe will collect the country, if we don't collect it on their behalf

So, all of this to say, Stripe require the billing country to be passed when we create a card payment method. We either collect it via the checkout fields and pass it to them, or they will collect it via the elements. It may be possible that other payment methods don't require the country, however, Stripe don't really outline when it would be required or not.


IIRC, on the shortcode checkout we determine what fields Stripe should collect based on which fields are present on the checkout. I'm not sure if that's possible on the block checkout, but that might be worth looking into.

@giilby
Copy link
Author

giilby commented Oct 30, 2024

@james-allan Thanks for the quick reply, and for digging into this! It's helpful to know that if we simply omit country (and zip) from our checkout form, they'll be collected in the Stripe one instead. That might be the way we should go, as I wasn't able to properly require the Country field (which WC wires up as a fancy-select that's connected to the State field). On that note, another question: it appears that when the checkout form's submit button is clicked, WC attempts to create the payment method (which happens on the client-side) before fully-validating the form...is this expected?

@james-allan
Copy link
Contributor

That might be the way we should go, as I wasn't able to properly require the Country field (which WC wires up as a fancy-select that's connected to the State field).

Do you mind elaborating or sharing a screenshot of what you mean by this? Is this a custom checkout you have running on your site or is it just a specific country where this kind of behaviour exists? On my checkout the default behaviour is that the country and state is required.

It sounds like you would benefit from what I mentioned at the bottom of my previous comment. ie there may be a way for us to tell Stripe what we collect via our checkout and they can collect whatever is missing and in their eyes required.

it appears that when the checkout form's submit button is clicked, WC attempts to create the payment method (which happens on the client-side) before fully-validating the form...is this expected?

I've been able to replicate that too. We should fix that.

@annemirasol
Copy link
Contributor

I looked at some of the possible next steps for the issues discussed above:

  1. Making sure Stripe collects fields that the checkout form doesn't.

when creating the elements we should tell Stripe what fields (on the checkout) we’re collecting. I think we do this on the classic checkout, but I don’t think we do on the block checkout.

  • Confirming that we are passing this info for classic (shortcode) checkout. For example, when billing country is removed from woocommerce_billing_fields, like shown below, the Stripe payment element box will display a dropdown for country, similar to what James has reported above.

    add_filter('woocommerce_billing_fields', 'disable_billing_country');
    
    function disable_billing_country($fields)
    {
        unset($fields['billing_country']);
        return $fields;
    }
    
  • For block checkout, currently, we configure Stripe to "never" collect billing address fields on its own. As the Checkout block provides very limited customization for address fields -- you can only customize "Company", "Line 2" and "Phone" -- I believe the current config for block checkout is appropriate.

Next step: none needed.

  1. Performing validation before payment request object is created.

We appear to be sending off the request to create the payment method before WC has validated the checkout fields. I was able to replicate it by trying to submit the classic checkout with no phone number field entered. I confirmed the createStripePaymentMethod() was called even though the checkout didn’t validate.

Ideally, we want to run form field validation before we pass things to Stripe API. However, the following things make it difficult to do so:

  • By design, checkout processing and the requisite form validation is handled by WooCommerce core. The validation happens after form submit, inside tightly-coupled protected methods, making it difficult to perform a separate pre-submit validation separately.
  • During validation, the payment method is expected to be already attached to the input form.
  • Adding our own, separate pre-submit validation code feels wrong -- redundant and fragile.
  • As a workaround, we could perhaps submit a form without payment, expecting it to fail validation, and from the results try to gather if there are non-payment related validation errors. This clearly needs more thinking, and off the bat doesn't feel the cleanest solution.

Next step: Create a separate issue to track this and document discussion.

  1. Improving the error message being displayed to customers.

Next step: In #3588, we will catch the error response from Stripe and output a friendlier message to the customer.

@giilby
Copy link
Author

giilby commented Nov 13, 2024

@james-allan Apologies for my delayed response! RE: being unable to properly require country (or state), the problem only exists with "Classic Checkout". Here's a screenshot from a stock WC installation with "Classic Checkout" enabled. You can see that Country / Region and State are both starred (and highlighted in red) indicating that they are configured to be required, but when you actually submit the checkout form with all other required fields filled out, validation doesn't actually occur, and you instead get the cryptic Stripe message. This is in contrast to the newer block-based checkout, which appears to avoid validating the credit card inputs until all other required fields have been submitted. You can easily see the difference in behavior by setting up a wordpress installation with just the WooCommerce and WooCommerce Stripe Gateway plugins, then switch between "Classic Checkout" and "Block-Based Checkout."

All that said, this may just be another problem related to the fact that classic checkout is very eager to attempt Stripe payment method creation. If the checkout form includes "Country / Region" and Stripe requires it to create a payment method, it would be ideal if the user was properly forced to select "Country / Region" first.

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants