forked from CGM_Public/pretix_original
Fix crash if a tax rule on a fee prevents sale (PRETIXEU-8MZ)
This commit is contained in:
@@ -60,7 +60,7 @@ from pretix.base.channels import get_all_sales_channels
|
|||||||
from pretix.base.forms import PlaceholderValidator
|
from pretix.base.forms import PlaceholderValidator
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Event, GiftCard, InvoiceAddress, Order, OrderPayment,
|
CartPosition, Event, GiftCard, InvoiceAddress, Order, OrderPayment,
|
||||||
OrderRefund, Quota,
|
OrderRefund, Quota, TaxRule,
|
||||||
)
|
)
|
||||||
from pretix.base.reldate import RelativeDateField, RelativeDateWrapper
|
from pretix.base.reldate import RelativeDateField, RelativeDateWrapper
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
@@ -1015,7 +1015,11 @@ class FreeOrderProvider(BasePaymentProvider):
|
|||||||
|
|
||||||
cart = get_cart(request)
|
cart = get_cart(request)
|
||||||
total = get_cart_total(request)
|
total = get_cart_total(request)
|
||||||
total += sum([f.value for f in get_fees(self.event, request, total, None, None, cart)])
|
try:
|
||||||
|
total += sum([f.value for f in get_fees(self.event, request, total, None, None, cart)])
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
# ignore for now, will fail on order creation
|
||||||
|
pass
|
||||||
return total == 0
|
return total == 0
|
||||||
|
|
||||||
def order_change_allowed(self, order: Order) -> bool:
|
def order_change_allowed(self, order: Order) -> bool:
|
||||||
|
|||||||
@@ -935,7 +935,10 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
|||||||
raise OrderError(e.message)
|
raise OrderError(e.message)
|
||||||
|
|
||||||
require_approval = any(p.requires_approval(invoice_address=address) for p in positions)
|
require_approval = any(p.requires_approval(invoice_address=address) for p in positions)
|
||||||
fees = _get_fees(positions, payment_requests, address, meta_info, event, require_approval=require_approval)
|
try:
|
||||||
|
fees = _get_fees(positions, payment_requests, address, meta_info, event, require_approval=require_approval)
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
raise OrderError(error_messages['country_blocked'])
|
||||||
total = pending_sum = sum([c.price for c in positions]) + sum([c.value for c in fees])
|
total = pending_sum = sum([c.price for c in positions]) + sum([c.value for c in fees])
|
||||||
|
|
||||||
order = Order(
|
order = Order(
|
||||||
@@ -968,7 +971,10 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
|||||||
|
|
||||||
for fee in fees:
|
for fee in fees:
|
||||||
fee.order = order
|
fee.order = order
|
||||||
fee._calculate_tax()
|
try:
|
||||||
|
fee._calculate_tax()
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
raise OrderError(error_messages['country_blocked'])
|
||||||
if fee.tax_rule and not fee.tax_rule.pk:
|
if fee.tax_rule and not fee.tax_rule.pk:
|
||||||
fee.tax_rule = None # TODO: deprecate
|
fee.tax_rule = None # TODO: deprecate
|
||||||
fee.save()
|
fee.save()
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ from django.views.generic import TemplateView
|
|||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
from paypalcheckoutsdk import orders as pp_orders, payments as pp_payments
|
from paypalcheckoutsdk import orders as pp_orders, payments as pp_payments
|
||||||
|
|
||||||
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
from pretix.base.models import (
|
||||||
|
Event, Order, OrderPayment, OrderRefund, Quota, TaxRule,
|
||||||
|
)
|
||||||
from pretix.base.payment import PaymentException
|
from pretix.base.payment import PaymentException
|
||||||
from pretix.base.services.cart import add_payment_to_cart, get_fees
|
from pretix.base.services.cart import add_payment_to_cart, get_fees
|
||||||
from pretix.base.settings import GlobalSettingsObject
|
from pretix.base.settings import GlobalSettingsObject
|
||||||
@@ -158,8 +160,12 @@ class XHRView(View):
|
|||||||
'info_data': {},
|
'info_data': {},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
for fee in get_fees(request.event, request, cart_total, None, simulated_payments, get_cart(request)):
|
try:
|
||||||
cart_total += fee.value
|
for fee in get_fees(request.event, request, cart_total, None, simulated_payments, get_cart(request)):
|
||||||
|
cart_total += fee.value
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
# ignore for now, will fail on order creation
|
||||||
|
pass
|
||||||
|
|
||||||
total_remaining = cart_total
|
total_remaining = cart_total
|
||||||
for p in multi_use_cart_payments:
|
for p in multi_use_cart_payments:
|
||||||
|
|||||||
@@ -1126,13 +1126,17 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
|||||||
def _total_order_value(self):
|
def _total_order_value(self):
|
||||||
cart = get_cart(self.request)
|
cart = get_cart(self.request)
|
||||||
total = get_cart_total(self.request)
|
total = get_cart_total(self.request)
|
||||||
total += sum([
|
try:
|
||||||
f.value for f in get_fees(
|
total += sum([
|
||||||
self.request.event, self.request, total, self.invoice_address,
|
f.value for f in get_fees(
|
||||||
[p for p in self.cart_session.get('payments', []) if p.get('multi_use_supported')],
|
self.request.event, self.request, total, self.invoice_address,
|
||||||
cart,
|
[p for p in self.cart_session.get('payments', []) if p.get('multi_use_supported')],
|
||||||
)
|
cart,
|
||||||
])
|
)
|
||||||
|
])
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
# ignore for now, will fail on order creation
|
||||||
|
pass
|
||||||
return Decimal(total)
|
return Decimal(total)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@@ -1201,6 +1205,7 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
|||||||
cart = self.get_cart(payments=simulated_payments)
|
cart = self.get_cart(payments=simulated_payments)
|
||||||
else:
|
else:
|
||||||
cart = self.get_cart()
|
cart = self.get_cart()
|
||||||
|
|
||||||
resp = pprov.checkout_prepare(
|
resp = pprov.checkout_prepare(
|
||||||
request,
|
request,
|
||||||
cart,
|
cart,
|
||||||
@@ -1283,8 +1288,12 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
|||||||
|
|
||||||
cart = get_cart(self.request)
|
cart = get_cart(self.request)
|
||||||
total = get_cart_total(self.request)
|
total = get_cart_total(self.request)
|
||||||
total += sum([f.value for f in get_fees(self.request.event, self.request, total, self.invoice_address,
|
try:
|
||||||
self.cart_session.get('payments', []), cart)])
|
total += sum([f.value for f in get_fees(self.request.event, self.request, total, self.invoice_address,
|
||||||
|
self.cart_session.get('payments', []), cart)])
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
# ignore for now, will fail on order creation
|
||||||
|
pass
|
||||||
selected = self.current_selected_payments(total, warn=warn, total_includes_payment_fees=True)
|
selected = self.current_selected_payments(total, warn=warn, total_includes_payment_fees=True)
|
||||||
if sum(p['payment_amount'] for p in selected) != total:
|
if sum(p['payment_amount'] for p in selected) != total:
|
||||||
if warn:
|
if warn:
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ from django_scopes import scopes_disabled
|
|||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Customer, InvoiceAddress, ItemAddOn, Question,
|
CartPosition, Customer, InvoiceAddress, ItemAddOn, Question,
|
||||||
QuestionAnswer, QuestionOption,
|
QuestionAnswer, QuestionOption, TaxRule,
|
||||||
)
|
)
|
||||||
from pretix.base.services.cart import get_fees
|
from pretix.base.services.cart import get_fees
|
||||||
from pretix.base.templatetags.money import money_filter
|
from pretix.base.templatetags.money import money_filter
|
||||||
@@ -198,11 +198,15 @@ class CartMixin:
|
|||||||
if order:
|
if order:
|
||||||
fees = order.fees.all()
|
fees = order.fees.all()
|
||||||
elif positions:
|
elif positions:
|
||||||
fees = get_fees(
|
try:
|
||||||
self.request.event, self.request, total, self.invoice_address,
|
fees = get_fees(
|
||||||
payments if payments is not None else self.cart_session.get('payments', []),
|
self.request.event, self.request, total, self.invoice_address,
|
||||||
cartpos
|
payments if payments is not None else self.cart_session.get('payments', []),
|
||||||
)
|
cartpos
|
||||||
|
)
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
# ignore for now, will fail on order creation
|
||||||
|
fees = []
|
||||||
else:
|
else:
|
||||||
fees = []
|
fees = []
|
||||||
|
|
||||||
@@ -389,7 +393,11 @@ def get_cart_is_free(request):
|
|||||||
pos = get_cart(request)
|
pos = get_cart(request)
|
||||||
ia = get_cart_invoice_address(request)
|
ia = get_cart_invoice_address(request)
|
||||||
total = get_cart_total(request)
|
total = get_cart_total(request)
|
||||||
fees = get_fees(request.event, request, total, ia, cs.get('payments', []), pos)
|
try:
|
||||||
|
fees = get_fees(request.event, request, total, ia, cs.get('payments', []), pos)
|
||||||
|
except TaxRule.SaleNotAllowed:
|
||||||
|
# ignore for now, will fail on order creation
|
||||||
|
fees = []
|
||||||
request._cart_free_cache = total + sum(f.value for f in fees) == Decimal('0.00')
|
request._cart_free_cache = total + sum(f.value for f in fees) == Decimal('0.00')
|
||||||
return request._cart_free_cache
|
return request._cart_free_cache
|
||||||
|
|
||||||
|
|||||||
@@ -456,6 +456,59 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
|||||||
cr1.refresh_from_db()
|
cr1.refresh_from_db()
|
||||||
assert cr1.price == Decimal('23.00')
|
assert cr1.price == Decimal('23.00')
|
||||||
|
|
||||||
|
def test_custom_tax_rules_blocked_on_fee(self):
|
||||||
|
self.tr7 = self.event.tax_rules.create(rate=7)
|
||||||
|
self.tr7.custom_rules = json.dumps([
|
||||||
|
{'country': 'AT', 'address_type': 'business_vat_id', 'action': 'reverse'},
|
||||||
|
{'country': 'ZZ', 'address_type': '', 'action': 'block'},
|
||||||
|
])
|
||||||
|
self.tr7.save()
|
||||||
|
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||||
|
self.event.settings.set('payment_banktransfer__fee_percent', 20)
|
||||||
|
self.event.settings.set('payment_banktransfer__fee_reverse_calc', False)
|
||||||
|
self.event.settings.set('tax_rate_default', self.tr7)
|
||||||
|
self.event.settings.invoice_address_vatid = True
|
||||||
|
|
||||||
|
with scopes_disabled():
|
||||||
|
CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'is_business': 'business',
|
||||||
|
'company': 'Foo',
|
||||||
|
'name': 'Bar',
|
||||||
|
'street': 'Baz',
|
||||||
|
'zipcode': '12345',
|
||||||
|
'city': 'Here',
|
||||||
|
'country': 'DE',
|
||||||
|
'email': 'admin@localhost'
|
||||||
|
}, follow=True)
|
||||||
|
|
||||||
|
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
|
||||||
|
mock_validate.return_value = 'AT123456'
|
||||||
|
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'is_business': 'business',
|
||||||
|
'company': 'Foo',
|
||||||
|
'name': 'Bar',
|
||||||
|
'street': 'Baz',
|
||||||
|
'zipcode': '1234',
|
||||||
|
'city': 'Here',
|
||||||
|
'country': 'AT',
|
||||||
|
'vat_id': 'AT123456',
|
||||||
|
'email': 'admin@localhost'
|
||||||
|
}, follow=True)
|
||||||
|
|
||||||
|
self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'payment': 'banktransfer'
|
||||||
|
}, follow=True)
|
||||||
|
|
||||||
|
r = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
|
doc = BeautifulSoup(r.content.decode(), "lxml")
|
||||||
|
assert doc.select(".alert-danger")
|
||||||
|
assert "not available in the selected country" in doc.select(".alert-danger")[0].text
|
||||||
|
|
||||||
def test_custom_tax_rules_blocked(self):
|
def test_custom_tax_rules_blocked(self):
|
||||||
self.tr19.custom_rules = json.dumps([
|
self.tr19.custom_rules = json.dumps([
|
||||||
{'country': 'AT', 'address_type': 'business_vat_id', 'action': 'reverse'},
|
{'country': 'AT', 'address_type': 'business_vat_id', 'action': 'reverse'},
|
||||||
|
|||||||
Reference in New Issue
Block a user