Repository: saleor/saleor

Issue:
Some invalid phone numbers throw an exception and result in server error
### What I'm trying to achieve
Type in a fake number +1 123 456 7890, into the shipping form. Should fail gracefully with a red input box

### Steps to reproduce the problem
1. Type in a fake number +1 123 456 7890, into the shipping form
2. Receive ValueError at /en/checkout/shipping-address/ “+11234567890” is not a valid phone number.

### What I expected to happen
Should fail gracefully with a red input box

### What happens
500 Internal server error

**System information**
Operating system: Ubuntu 18.04
Browser: Firefox



Discussion:
We should check `saleor/account/i18n.py::clean_phone_for_country`

Resolving patch:
diff --git a/saleor/account/i18n.py b/saleor/account/i18n.py
--- a/saleor/account/i18n.py
+++ b/saleor/account/i18n.py
@@ -3,13 +3,11 @@
 import i18naddress
 from django import forms
 from django.forms.forms import BoundField
-from django.utils.translation import pgettext_lazy, ugettext_lazy as _
+from django.utils.translation import pgettext_lazy
 from django_countries import countries
-from phonenumber_field.phonenumber import PhoneNumber
-from phonenumbers import NumberParseException
-from phonenumbers.phonenumberutil import is_possible_number
 
 from .models import Address
+from .validators import validate_possible_number
 from .widgets import DatalistTextWidget, PhonePrefixWidget
 
 COUNTRY_FORMS = {}
@@ -145,26 +143,12 @@ def clean(self):
         country = data.get("country")
         if phone:
             try:
-                data["phone"] = clean_phone_for_country(phone, country)
+                data["phone"] = validate_possible_number(phone, country)
             except forms.ValidationError as error:
                 self.add_error("phone", error)
         return data
 
 
-def clean_phone_for_country(phone, country):
-    error = _("The phone number entered is not valid.")
-    error_code = "invalid_phone_number"
-    if phone:
-        try:
-            phone = PhoneNumber.from_string(phone, country)
-        except NumberParseException:
-            raise forms.ValidationError(error, code=error_code)
-        else:
-            if not is_possible_number(phone):
-                raise forms.ValidationError(error, code=error_code)
-    return phone
-
-
 class CountryAwareAddressForm(AddressForm):
 
     I18N_MAPPING = [
diff --git a/saleor/account/validators.py b/saleor/account/validators.py
--- a/saleor/account/validators.py
+++ b/saleor/account/validators.py
@@ -4,9 +4,14 @@
 from phonenumbers.phonenumberutil import is_possible_number
 
 
-def validate_possible_number(value, country=None):
-    phone_number = to_python(value, country)
-    if phone_number and not is_possible_number(phone_number):
+def validate_possible_number(phone, country=None):
+    phone_number = to_python(phone, country)
+    if (
+        phone_number
+        and not is_possible_number(phone_number)
+        or not phone_number.is_valid()
+    ):
         raise ValidationError(
             _("The phone number entered is not valid."), code="invalid_phone_number"
         )
+    return phone_number


Test patch:
diff --git a/tests/test_account.py b/tests/test_account.py
--- a/tests/test_account.py
+++ b/tests/test_account.py
@@ -74,6 +74,24 @@ def test_address_form_postal_code_validation():
     assert "postal_code" in errors
 
 
+@pytest.mark.parametrize(
+    "country, phone, is_valid",
+    (
+        ("US", "123-456-7890", False),
+        ("US", "(541) 754-3010", True),
+        ("FR", "0600000000", True),
+    ),
+)
+def test_address_form_phone_number_validation(country, phone, is_valid):
+    data = {"country": country, "phone": phone}
+    form = forms.get_address_form(data, country_code="PL")[0]
+    errors = form.errors
+    if not is_valid:
+        assert "phone" in errors
+    else:
+        assert "phone" not in errors
+
+
 @pytest.mark.parametrize(
     "form_data, form_valid, expected_preview, expected_country",
     [
@@ -127,21 +145,32 @@ def test_country_aware_form_has_only_supported_countries():
 
 
 @pytest.mark.parametrize(
-    "input,exception",
-    [
-        ("123", ValidationError),
-        ("+48123456789", None),
-        ("+12025550169", None),
-        ("+481234567890", ValidationError),
-        ("testext", ValidationError),
-    ],
+    "input_data, is_valid",
+    (
+        ({"phone": "123"}, False),
+        ({"phone": "+48123456789"}, True),
+        ({"phone": "+12025550169"}, True),
+        ({"phone": "+481234567890"}, False),
+        ({"phone": "testext"}, False),
+        ({"phone": "1-541-754-3010"}, False),
+        ({"phone": "001-541-754-3010"}, False),
+        ({"phone": "+1-541-754-3010"}, True),
+        ({"country": "US", "phone": "123-456-7890"}, False),
+        ({"country": "US", "phone": "555-555-5555"}, False),
+        ({"country": "US", "phone": "754-3010"}, False),
+        ({"country": "US", "phone": "001-541-754-3010"}, False),
+        ({"country": "US", "phone": "(541) 754-3010"}, True),
+        ({"country": "US", "phone": "1-541-754-3010"}, True),
+        ({"country": "FR", "phone": "1234567890"}, False),
+        ({"country": "FR", "phone": "0600000000"}, True),
+    ),
 )
-def test_validate_possible_number(input, exception):
-    if exception is not None:
-        with pytest.raises(exception):
-            validate_possible_number(input)
+def test_validate_possible_number(input_data, is_valid):
+    if not is_valid:
+        with pytest.raises(ValidationError):
+            validate_possible_number(**input_data)
     else:
-        validate_possible_number(input)
+        validate_possible_number(**input_data)
 
 
 def test_order_with_lines_pagination(authorized_client, order_list, settings):