From d29a5cdb445f13591ad9b7c590cb8ddba3368dfc Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 26 Sep 2024 17:55:53 +0200 Subject: [PATCH] Add the new 3DSv2 fields --- core/static/core/style.scss | 6 + counter/admin.py | 1 + counter/forms.py | 1 + .../0023_billinginfo_phone_number.py | 20 ++ counter/models.py | 11 + counter/tests/test_counter.py | 39 ++- eboutic/schemas.py | 5 + .../eboutic/eboutic_makecommand.jinja | 23 +- eboutic/views.py | 20 +- locale/fr/LC_MESSAGES/django.po | 279 ++++++++++-------- 10 files changed, 264 insertions(+), 141 deletions(-) create mode 100644 counter/migrations/0023_billinginfo_phone_number.py diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 8fbc665e..9a6e07d9 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -306,6 +306,12 @@ a:not(.button) { align-items: center; text-align: justify; + &.alert-yellow { + background-color: rgb(255, 255, 240); + color: rgb(99, 87, 6); + border: rgb(192, 180, 16) 1px solid; + } + &.alert-green { background-color: rgb(245, 255, 245); color: rgb(3, 84, 63); diff --git a/counter/admin.py b/counter/admin.py index c0e9fac8..6940d50b 100644 --- a/counter/admin.py +++ b/counter/admin.py @@ -46,6 +46,7 @@ class CustomerAdmin(SearchModelAdmin): @admin.register(BillingInfo) class BillingInfoAdmin(admin.ModelAdmin): list_display = ("first_name", "last_name", "address_1", "city", "country") + autocomplete_fields = ("customer",) @admin.register(Counter) diff --git a/counter/forms.py b/counter/forms.py index 734cfcf6..a5950eea 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -26,6 +26,7 @@ class BillingInfoForm(forms.ModelForm): "zip_code", "city", "country", + "phone_number", ] diff --git a/counter/migrations/0023_billinginfo_phone_number.py b/counter/migrations/0023_billinginfo_phone_number.py new file mode 100644 index 00000000..86413f46 --- /dev/null +++ b/counter/migrations/0023_billinginfo_phone_number.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.16 on 2024-09-26 10:28 + +import phonenumber_field.modelfields +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("counter", "0022_alter_product_icon"), + ] + + operations = [ + migrations.AddField( + model_name="billinginfo", + name="phone_number", + field=phonenumber_field.modelfields.PhoneNumberField( + max_length=128, null=True, region=None, verbose_name="Phone number" + ), + ), + ] diff --git a/counter/models.py b/counter/models.py index c068328f..6d4971eb 100644 --- a/counter/models.py +++ b/counter/models.py @@ -34,6 +34,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from django_countries.fields import CountryField +from phonenumber_field.modelfields import PhoneNumberField from accounting.models import CurrencyField from club.models import Club @@ -176,6 +177,14 @@ class BillingInfo(models.Model): city = models.CharField(_("City"), max_length=50) country = CountryField(blank_label=_("Country")) + # This table was created during the A22 semester. + # However, later on, CA asked for the phone number to be added to the billing info. + # As the table was already created, this new field had to be nullable, + # even tough it is required by the bank and shouldn't be null. + # If one day there is no null phone number remaining, + # please make the field non-nullable. + phone_number = PhoneNumberField(_("Phone number"), null=True, blank=False) + def __str__(self): return f"{self.first_name} {self.last_name}" @@ -192,6 +201,8 @@ class BillingInfo(models.Model): "ZipCode": self.zip_code, "City": self.city, "CountryCode": self.country.numeric, # ISO-3166-1 numeric code + "MobilePhone": self.phone_number.as_national.replace(" ", ""), + "CountryCodeMobilePhone": f"+{self.phone_number.country_code}", } } if self.address_2: diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index 3dc0ec74..85ea1dfa 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -315,6 +315,7 @@ class TestBillingInfo: "zip_code": "34301", "city": "Sète", "country": "FR", + "phone_number": "0612345678", } def test_edit_infos(self, client: Client, payload: dict): @@ -356,7 +357,7 @@ class TestBillingInfo: for key, val in payload.items(): assert getattr(infos, key) == val - def test_invalid_data(self, client: Client, payload): + def test_invalid_data(self, client: Client, payload: dict[str, str]): user = subscriber_user.make() client.force_login(user) # address_1, zip_code and country are missing @@ -391,6 +392,42 @@ class TestBillingInfo: ) assert response.status_code == expected_code + @pytest.mark.parametrize( + "phone_number", + ["+33612345678", "0612345678", "06 12 34 56 78", "06-12-34-56-78"], + ) + def test_phone_number_format( + self, client: Client, payload: dict, phone_number: str + ): + """Test that various formats of phone numbers are accepted.""" + user = subscriber_user.make() + client.force_login(user) + payload["phone_number"] = phone_number + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == 200 + infos = BillingInfo.objects.get(customer__user=user) + assert infos.phone_number == "0612345678" + assert infos.phone_number.country_code == 33 + + def test_foreign_phone_number(self, client: Client, payload: dict): + """Test that a foreign phone number is accepted.""" + user = subscriber_user.make() + client.force_login(user) + payload["phone_number"] = "+49612345678" + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == 200 + infos = BillingInfo.objects.get(customer__user=user) + assert infos.phone_number.as_national == "06123 45678" + assert infos.phone_number.country_code == 49 + class TestBarmanConnection(TestCase): @classmethod diff --git a/eboutic/schemas.py b/eboutic/schemas.py index c0333454..940d6ddb 100644 --- a/eboutic/schemas.py +++ b/eboutic/schemas.py @@ -31,3 +31,8 @@ class BillingInfoSchema(ModelSchema): "country", ] fields_optional = ["customer"] + + # for reasons described in the model, BillingInfo.phone_number + # in nullable, but null values shouldn't be actually allowed, + # so we force the field to be required + phone_number: str diff --git a/eboutic/templates/eboutic/eboutic_makecommand.jinja b/eboutic/templates/eboutic/eboutic_makecommand.jinja index 30a8036a..85e6b501 100644 --- a/eboutic/templates/eboutic/eboutic_makecommand.jinja +++ b/eboutic/templates/eboutic/eboutic_makecommand.jinja @@ -98,13 +98,20 @@
- {% if must_fill_billing_infos %} -

- - {% trans %}You must fill your billing infos if you want to pay with your credit - card{% endtrans %} - -

+ {% if billing_infos_state == BillingInfoState.EMPTY %} +
+ {% trans %}You must fill your billing infos if you want to pay with your credit + card{% endtrans %} +
+ {% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %} +
+ {% trans %} + The Crédit Agricole changed its policy related to the billing + information that must be provided in order to pay with a credit card. + If you want to pay with your credit card, you must add a phone number + to the data you already provided. + {% endtrans %} +
{% endif %}