-
Notifications
You must be signed in to change notification settings - Fork 43
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
Organiser profile billing settings option missing #411
Open
odkhang
wants to merge
32
commits into
fossasia:development
Choose a base branch
from
odkhang:feature-380
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
75a08f6
Implement billing settings form for organizer
odkhang b0d7932
Resolve conflict
odkhang 9b499db
Fix flake8 in pipeline
odkhang a17173a
implement create and save payment_information
odkhang 055b2d7
Fix isort, flake8 in pipeline
odkhang 045bfe4
implement payment-information-v1
odkhang e7510d2
src/pretix/control/views/organizer_views/organizer_view.py
odkhang 0c4b5f6
Code refactoring
odkhang f596f95
Code refactoring
odkhang ede87ff
Remove payment_information attribute
odkhang eb22165
Implement tax validation
odkhang 8cf14e5
Update code
odkhang 25f70a9
Update code
odkhang abc5731
Fix flake8 in pipeline
odkhang dbf24b2
Add pyvat package
odkhang 7b1fe49
Fix flake8 in pipeline
odkhang 27b71a6
Latest code
odkhang 063d2a6
Fix flake8 in pipeline
odkhang c4b9ce5
Add logger error
odkhang 7133019
Fix flake8 in pipeline
odkhang 97fbc9c
Merge branch 'development' into feature-380
odkhang 8b5f87e
Fix conflict pretix base migration
odkhang 17064b1
Update pretix base migration
odkhang db4f7b2
Add logging information and modify error logging
odkhang 5d5e638
Add comment,save tax_id, show error and sucess message
odkhang 13b2944
Update code
odkhang 38154c7
Fix flake8 in pipeline
odkhang ea52541
Merge branch 'development' into feature-380
odkhang 8512f8c
Update code
odkhang 4aa0e3c
Update code
odkhang e41650b
Update code
odkhang aa86746
fix flake8 in pipeline
odkhang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
...etix/base/migrations/0003_alter_cachedcombinedticket_id_alter_cachedticket_id_and_more.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Generated by Django 5.1.2 on 2024-10-31 09:30 | ||
|
||
import django.db.models.deletion | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("pretixbase", "0003_event_is_video_creation_and_more"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="OrganizerBillingModel", | ||
fields=[ | ||
( | ||
"id", | ||
models.BigAutoField( | ||
auto_created=True, primary_key=True, serialize=False | ||
), | ||
), | ||
("primary_contact_name", models.CharField(max_length=255)), | ||
("primary_contact_email", models.EmailField(max_length=255)), | ||
("company_or_organization_name", models.CharField(max_length=255)), | ||
("address_line_1", models.CharField(max_length=255)), | ||
("address_line_2", models.CharField(max_length=255)), | ||
("city", models.CharField(max_length=255)), | ||
("zip_code", models.CharField(max_length=255)), | ||
("country", models.CharField(max_length=255)), | ||
("preferred_language", models.CharField(max_length=255)), | ||
("tax_id", models.CharField(max_length=255)), | ||
("stripe_customer_id", models.CharField(max_length=255, null=True)), | ||
( | ||
"stripe_payment_method_id", | ||
models.CharField(max_length=255, null=True), | ||
), | ||
("stripe_setup_intent_id", models.CharField(max_length=255, null=True)), | ||
( | ||
"organizer", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="billing", | ||
to="pretixbase.organizer", | ||
), | ||
), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,14 @@ | ||
import pyvat | ||
from django import forms | ||
from django.conf import settings | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from pretix.base.forms import I18nModelForm | ||
from pretix.base.models.organizer import Organizer | ||
from pretix.base.models.organizer import Organizer, OrganizerBillingModel | ||
from pretix.helpers.countries import CachedCountries | ||
from pretix.helpers.stripe_utils import ( | ||
create_stripe_customer, update_customer_info, | ||
) | ||
|
||
|
||
class OrganizerForm(I18nModelForm): | ||
|
@@ -22,3 +28,187 @@ def clean_slug(self): | |
code='duplicate_slug', | ||
) | ||
return slug | ||
|
||
|
||
class BillingSettingsForm(forms.ModelForm): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (complexity): Consider refactoring form field definitions into a declarative specification dictionary to reduce code duplication. The form fields can be simplified using a declarative approach while maintaining readability. Here's how: class BillingSettingsForm(forms.ModelForm):
FIELD_SPECS = {
'primary_contact_name': {
'type': forms.CharField,
'label': _("Primary Contact Name"),
'help_text': _("Please provide your name or the name of the person responsible for this account in your organization."),
'required': True,
},
'primary_contact_email': {
'type': forms.EmailField,
'label': _("Primary Contact Email"),
'help_text': _("We will use this email address for all communication related to your contract and billing, "
"as well as for important updates about your account and our services."),
'required': True,
},
# ... define other fields similarly
}
class Meta:
model = OrganizerBillingModel
fields = list(FIELD_SPECS.keys())
def __init__(self, *args, **kwargs):
self.organizer = kwargs.pop("organizer", None)
super().__init__(*args, **kwargs)
# Generate fields from specifications
for field_name, specs in self.FIELD_SPECS.items():
field_type = specs.pop('type')
self.fields[field_name] = field_type(
max_length=255,
widget=forms.TextInput(attrs={"placeholder": ""}),
**specs
)
# Special handling for specific fields
self.fields['country'].widget = forms.Select(choices=CachedCountries())
self._setup_language_field()
self.set_initial_data() This approach:
|
||
class Meta: | ||
model = OrganizerBillingModel | ||
fields = [ | ||
"primary_contact_name", | ||
"primary_contact_email", | ||
"company_or_organization_name", | ||
"address_line_1", | ||
"address_line_2", | ||
"zip_code", | ||
"city", | ||
"country", | ||
"preferred_language", | ||
"tax_id", | ||
] | ||
|
||
primary_contact_name = forms.CharField( | ||
label=_("Primary Contact Name"), | ||
help_text=_( | ||
"Please provide your name or the name of the person responsible for this account in your organization." | ||
), | ||
required=True, | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
) | ||
|
||
primary_contact_email = forms.EmailField( | ||
label=_("Primary Contact Email"), | ||
help_text=_( | ||
"We will use this email address for all communication related to your contract and billing, " | ||
"as well as for important updates about your account and our services." | ||
), | ||
required=True, | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
) | ||
|
||
company_or_organization_name = forms.CharField( | ||
label=_("Company or Organization Name"), | ||
help_text=_("Enter your organization’s legal name."), | ||
required=True, | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
) | ||
|
||
address_line_1 = forms.CharField( | ||
label=_("Address Line 1"), | ||
help_text=_("Street address or P.O. box."), | ||
required=True, | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
) | ||
|
||
address_line_2 = forms.CharField( | ||
label=_("Address Line 2"), | ||
help_text=_("Apartment, suite, unit, etc. (optional)."), | ||
required=False, | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
) | ||
|
||
zip_code = forms.CharField( | ||
label=_("Zip Code"), | ||
help_text=_("Enter your postal code."), | ||
required=True, | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
) | ||
|
||
city = forms.CharField( | ||
label=_("City"), | ||
help_text=_("Enter your city."), | ||
required=True, | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
) | ||
|
||
country = forms.ChoiceField( | ||
label=_("Country"), | ||
help_text=_("Select your country."), | ||
required=True, | ||
choices=CachedCountries(), | ||
initial="US", | ||
) | ||
|
||
preferred_language = forms.ChoiceField( | ||
label=_("Preferred Language for Correspondence"), | ||
help_text=_("Select your preferred language for all communication."), | ||
required=True, | ||
) | ||
|
||
tax_id = forms.CharField( | ||
label=_("Tax ID (e.g., VAT, GST)"), | ||
help_text=_( | ||
"If you are located in the EU, please provide your VAT ID. " | ||
"Without this, we will need to charge VAT on our services and will not be able to issue reverse charge invoices." | ||
), | ||
max_length=255, | ||
widget=forms.TextInput(attrs={"placeholder": ""}), | ||
required=False, | ||
) | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.organizer = kwargs.pop("organizer", None) | ||
self.warning_message = None | ||
super().__init__(*args, **kwargs) | ||
selected_languages = [ | ||
(code, name) | ||
for code, name in settings.LANGUAGES | ||
if code in self.organizer.settings.locales | ||
] | ||
self.fields["preferred_language"].choices = selected_languages | ||
self.fields["preferred_language"].initial = self.organizer.settings.locale | ||
self.set_initial_data() | ||
|
||
def set_initial_data(self): | ||
billing_settings = OrganizerBillingModel.objects.filter( | ||
organizer_id=self.organizer.id | ||
).first() | ||
|
||
if billing_settings: | ||
for field in self.Meta.fields: | ||
self.initial[field] = getattr(billing_settings, field, "") | ||
|
||
@staticmethod | ||
def get_country_name(country_code): | ||
country = CachedCountries().countries | ||
return country.get(country_code, None) | ||
|
||
def validate_vat_number(self, country_code, vat_number): | ||
if country_code not in pyvat.VAT_REGISTRIES: | ||
country_name = self.get_country_name(country_code) | ||
self.warning_message = _("VAT number validation is not supported for {}".format(country_name)) | ||
return True | ||
result = pyvat.is_vat_number_format_valid(vat_number, country_code) | ||
return result | ||
|
||
def is_valid(self): | ||
if not super().is_valid(): | ||
return False | ||
|
||
cleaned_data = self.cleaned_data | ||
country_code = cleaned_data.get("country") | ||
vat_number = cleaned_data.get("tax_id") | ||
|
||
if vat_number: | ||
country_name = self.get_country_name(country_code) | ||
is_valid_vat_number = self.validate_vat_number(country_code, vat_number) | ||
if not is_valid_vat_number: | ||
self.add_error("tax_id", _("Invalid VAT number for {}".format(country_name))) | ||
return False | ||
return True | ||
|
||
def save(self, commit=True): | ||
instance = OrganizerBillingModel.objects.filter( | ||
organizer_id=self.organizer.id | ||
).first() | ||
|
||
if instance: | ||
for field in self.Meta.fields: | ||
setattr(instance, field, self.cleaned_data[field]) | ||
|
||
if commit: | ||
update_customer_info( | ||
instance.stripe_customer_id, | ||
email=self.cleaned_data.get("primary_contact_email"), | ||
name=self.cleaned_data.get("primary_contact_name"), | ||
) | ||
instance.save() | ||
else: | ||
instance = OrganizerBillingModel(organizer_id=self.organizer.id) | ||
for field in self.Meta.fields: | ||
setattr(instance, field, self.cleaned_data[field]) | ||
|
||
if commit: | ||
stripe_customer = create_stripe_customer( | ||
email=self.cleaned_data.get("primary_contact_email"), | ||
name=self.cleaned_data.get("primary_contact_name") | ||
) | ||
instance.stripe_customer_id = stripe_customer.id | ||
instance.save() | ||
return instance |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider using a data structure to define common form fields with shared properties.
The form field definitions can be simplified by using a data structure to define common fields while keeping special cases explicit. This reduces duplication while maintaining clarity:
This approach: