forked from CGM_Public/pretix_original
Allow to keep cancellation fees (#1130)
* Allow to keep cancellation fees * Add tests and clarifications * Add API
This commit is contained in:
@@ -363,7 +363,7 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '25.00',
|
||||
'mark_refunded': False
|
||||
'mark_canceled': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Invalid refund amount, only 23.00 are available to refund.']}
|
||||
@@ -372,7 +372,7 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '20.00',
|
||||
'mark_refunded': False
|
||||
'mark_canceled': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Partial refund not available for this payment method.']}
|
||||
@@ -380,7 +380,7 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/refund/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'mark_refunded': False
|
||||
'mark_canceled': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Full refund not available for this payment method.']}
|
||||
@@ -389,7 +389,7 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': False
|
||||
'mark_canceled': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Full refund not available for this payment method.']}
|
||||
@@ -398,7 +398,7 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': False
|
||||
'mark_canceled': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'detail': 'Invalid state of payment.'}
|
||||
@@ -431,7 +431,7 @@ def test_payment_refund_success(token_client, organizer, event, order, monkeypat
|
||||
organizer.slug, event.slug, order.code, p1.local_id
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': False,
|
||||
'mark_canceled': False,
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
r = order.refunds.get(local_id=resp.data['local_id'])
|
||||
@@ -464,7 +464,7 @@ def test_payment_refund_unavailable(token_client, organizer, event, order, monke
|
||||
organizer.slug, event.slug, order.code, p1.local_id
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': False,
|
||||
'mark_canceled': False,
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'detail': 'External error: We had trouble communicating with Stripe. Please try again and contact support if the problem persists.'}
|
||||
@@ -514,7 +514,7 @@ def test_refund_process_mark_refunded(token_client, organizer, event, order):
|
||||
p.create_external_refund()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/2/process/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={'mark_refunded': True})
|
||||
), format='json', data={'mark_canceled': True})
|
||||
r = order.refunds.get(local_id=1)
|
||||
assert resp.status_code == 200
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
@@ -523,7 +523,7 @@ def test_refund_process_mark_refunded(token_client, organizer, event, order):
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/2/process/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={'mark_refunded': True})
|
||||
), format='json', data={'mark_canceled': True})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@@ -533,7 +533,7 @@ def test_refund_process_mark_pending(token_client, organizer, event, order):
|
||||
p.create_external_refund()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/2/process/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={'mark_refunded': False})
|
||||
), format='json', data={'mark_canceled': False})
|
||||
r = order.refunds.get(local_id=1)
|
||||
assert resp.status_code == 200
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
@@ -955,6 +955,20 @@ def test_order_mark_canceled_pending(token_client, organizer, event, order):
|
||||
assert len(djmail.outbox) == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_mark_canceled_pending_fee_not_allowed(token_client, organizer, event, order):
|
||||
djmail.outbox = []
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/mark_canceled/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), data={
|
||||
'cancellation_fee': '7.00'
|
||||
}
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'detail': 'The cancellation fee cannot be higher than the payment credit of this order.'}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_mark_canceled_pending_no_email(token_client, organizer, event, order):
|
||||
djmail.outbox = []
|
||||
@@ -984,6 +998,25 @@ def test_order_mark_canceled_expired(token_client, organizer, event, order):
|
||||
assert order.status == Order.STATUS_EXPIRED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_mark_paid_canceled_keep_fee(token_client, organizer, event, order):
|
||||
order.status = Order.STATUS_PAID
|
||||
order.save()
|
||||
order.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=order.total)
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/mark_canceled/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), data={
|
||||
'cancellation_fee': '6.00'
|
||||
}
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['status'] == Order.STATUS_PAID
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
assert order.total == Decimal('6.00')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_mark_paid_refunded(token_client, organizer, event, order):
|
||||
order.status = Order.STATUS_PAID
|
||||
@@ -2415,7 +2448,7 @@ def test_refund_create(token_client, organizer, event, order):
|
||||
@pytest.mark.django_db
|
||||
def test_refund_create_mark_refunded(token_client, organizer, event, order):
|
||||
res = copy.deepcopy(REFUND_CREATE_PAYLOAD)
|
||||
res['mark_refunded'] = True
|
||||
res['mark_canceled'] = True
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
|
||||
@@ -127,6 +127,19 @@ class QuotaTestCase(BaseQuotaTestCase):
|
||||
|
||||
self.assertEqual(quota2.availability(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
def test_position_canceled(self):
|
||||
self.quota.items.add(self.item1)
|
||||
self.quota.size = 3
|
||||
self.quota.save()
|
||||
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
|
||||
expires=now() + timedelta(days=3),
|
||||
total=4)
|
||||
op = OrderPosition.objects.create(order=order, item=self.item1, price=2)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
|
||||
op.canceled = True
|
||||
op.save()
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 3))
|
||||
|
||||
def test_reserved(self):
|
||||
self.quota.items.add(self.item1)
|
||||
self.quota.size = 3
|
||||
|
||||
@@ -11,7 +11,7 @@ from tests.base import SoupTest
|
||||
from tests.plugins.stripe.test_provider import MockedCharge
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
|
||||
Event, InvoiceAddress, Item, Order, OrderFee, OrderPayment, OrderPosition,
|
||||
OrderRefund, Organizer, Question, QuestionAnswer, Quota, Team, User,
|
||||
)
|
||||
from pretix.base.payment import PaymentException
|
||||
@@ -307,6 +307,91 @@ def test_order_cancel_free(client, env):
|
||||
assert o.status == Order.STATUS_CANCELED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_cancel_paid_keep_fee(client, env):
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=o.total)
|
||||
o.status = Order.STATUS_PAID
|
||||
o.save()
|
||||
tr7 = o.event.tax_rules.create(rate=Decimal('7.00'))
|
||||
o.event.settings.tax_rate_default = tr7
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
|
||||
'status': 'c',
|
||||
'cancellation_fee': '6.00'
|
||||
})
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
assert not o.positions.exists()
|
||||
assert o.all_positions.exists()
|
||||
f = o.fees.get()
|
||||
assert f.fee_type == OrderFee.FEE_TYPE_CANCELLATION
|
||||
assert f.value == Decimal('6.00')
|
||||
assert f.tax_value == Decimal('0.39')
|
||||
assert f.tax_rate == Decimal('7')
|
||||
assert f.tax_rule == tr7
|
||||
assert o.status == Order.STATUS_PAID
|
||||
assert o.total == Decimal('6.00')
|
||||
assert o.pending_sum == Decimal('-8.00')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_cancel_pending_keep_fee(client, env):
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=Decimal('8.00'))
|
||||
o.status = Order.STATUS_PENDING
|
||||
o.save()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
|
||||
'status': 'c',
|
||||
'cancellation_fee': '6.00'
|
||||
})
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
assert not o.positions.exists()
|
||||
assert o.all_positions.exists()
|
||||
f = o.fees.get()
|
||||
assert f.fee_type == OrderFee.FEE_TYPE_CANCELLATION
|
||||
assert f.value == Decimal('6.00')
|
||||
assert o.status == Order.STATUS_PAID
|
||||
assert o.total == Decimal('6.00')
|
||||
assert o.pending_sum == Decimal('-2.00')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_cancel_pending_fee_too_high(client, env):
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=Decimal('4.00'))
|
||||
o.status = Order.STATUS_PENDING
|
||||
o.save()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
|
||||
'status': 'c',
|
||||
'cancellation_fee': '6.00'
|
||||
})
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
assert o.positions.exists()
|
||||
assert not o.fees.exists()
|
||||
assert o.status == Order.STATUS_PENDING
|
||||
assert o.total == Decimal('14.00')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_cancel_unpaid_no_fees_allowed(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
|
||||
'status': 'c',
|
||||
'cancellation_fee': '6.00'
|
||||
})
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
assert o.positions.exists()
|
||||
assert not o.fees.exists()
|
||||
assert o.status == Order.STATUS_CANCELED
|
||||
assert o.total == Decimal('14.00')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_invoice_create_forbidden(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
Reference in New Issue
Block a user