mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
Fix #571 -- Partial payments and refunds
This commit is contained in:
@@ -41,7 +41,7 @@ def order(event, item, other_item, taxrule):
|
||||
status=Order.STATUS_PAID, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC),
|
||||
expires=datetime.datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC),
|
||||
total=46, payment_provider='banktransfer', locale='en'
|
||||
total=46, locale='en'
|
||||
)
|
||||
InvoiceAddress.objects.create(order=o, company="Sample company", country=Country('NZ'))
|
||||
OrderPosition.objects.create(
|
||||
|
||||
@@ -31,7 +31,7 @@ def order(event, item, taxrule):
|
||||
status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC),
|
||||
expires=datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
|
||||
tax_value=Decimal('0.05'), tax_rule=taxrule)
|
||||
@@ -479,7 +479,7 @@ def test_event_update_live_no_product(token_client, organizer, event):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_event_update_live_no_payment_method(token_client, organizer, event, item):
|
||||
def test_event_update_live_no_payment_method(token_client, organizer, event, item, free_quota):
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug),
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ def order(event, item, taxrule):
|
||||
status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC),
|
||||
expires=datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
|
||||
tax_value=Decimal('0.05'), tax_rule=taxrule)
|
||||
|
||||
@@ -9,9 +9,13 @@ from django.core import mail as djmail
|
||||
from django.utils.timezone import now
|
||||
from django_countries.fields import Country
|
||||
from pytz import UTC
|
||||
from stripe.error import APIConnectionError
|
||||
from tests.plugins.stripe.test_provider import MockedCharge
|
||||
|
||||
from pretix.base.models import InvoiceAddress, Order, OrderPosition, Question
|
||||
from pretix.base.models.orders import CartPosition, OrderFee
|
||||
from pretix.base.models.orders import (
|
||||
CartPosition, OrderFee, OrderPayment, OrderRefund,
|
||||
)
|
||||
from pretix.base.services.invoices import (
|
||||
generate_cancellation, generate_invoice,
|
||||
)
|
||||
@@ -57,6 +61,8 @@ def quota(event, item):
|
||||
@pytest.fixture
|
||||
def order(event, item, taxrule, question):
|
||||
testtime = datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC)
|
||||
event.plugins += ",pretix.plugins.stripe"
|
||||
event.save()
|
||||
|
||||
with mock.patch('django.utils.timezone.now') as mock_now:
|
||||
mock_now.return_value = testtime
|
||||
@@ -65,7 +71,26 @@ def order(event, item, taxrule, question):
|
||||
status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=UTC),
|
||||
expires=datetime.datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
p1 = o.payments.create(
|
||||
provider='stripe',
|
||||
state='refunded',
|
||||
amount=Decimal('23.00'),
|
||||
payment_date=testtime,
|
||||
)
|
||||
o.refunds.create(
|
||||
provider='stripe',
|
||||
state='done',
|
||||
source='admin',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=testtime,
|
||||
payment=p1,
|
||||
)
|
||||
o.payments.create(
|
||||
provider='banktransfer',
|
||||
state='pending',
|
||||
amount=Decimal('23.00'),
|
||||
)
|
||||
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
|
||||
tax_value=Decimal('0.05'), tax_rule=taxrule)
|
||||
@@ -112,6 +137,36 @@ TEST_ORDERPOSITION_RES = {
|
||||
],
|
||||
"subevent": None
|
||||
}
|
||||
TEST_PAYMENTS_RES = [
|
||||
{
|
||||
"local_id": 1,
|
||||
"created": "2017-12-01T10:00:00Z",
|
||||
"payment_date": "2017-12-01T10:00:00Z",
|
||||
"provider": "stripe",
|
||||
"state": "refunded",
|
||||
"amount": "23.00"
|
||||
},
|
||||
{
|
||||
"local_id": 2,
|
||||
"created": "2017-12-01T10:00:00Z",
|
||||
"payment_date": None,
|
||||
"provider": "banktransfer",
|
||||
"state": "pending",
|
||||
"amount": "23.00"
|
||||
}
|
||||
]
|
||||
TEST_REFUNDS_RES = [
|
||||
{
|
||||
"local_id": 1,
|
||||
"payment": 1,
|
||||
"source": "admin",
|
||||
"created": "2017-12-01T10:00:00Z",
|
||||
"execution_date": "2017-12-01T10:00:00Z",
|
||||
"provider": "stripe",
|
||||
"state": "done",
|
||||
"amount": "23.00"
|
||||
},
|
||||
]
|
||||
TEST_ORDER_RES = {
|
||||
"code": "FOO",
|
||||
"status": "n",
|
||||
@@ -120,7 +175,7 @@ TEST_ORDER_RES = {
|
||||
"locale": "en",
|
||||
"datetime": "2017-12-01T10:00:00Z",
|
||||
"expires": "2017-12-10T10:00:00Z",
|
||||
"payment_date": None,
|
||||
"payment_date": "2017-12-01",
|
||||
"fees": [
|
||||
{
|
||||
"fee_type": "payment",
|
||||
@@ -149,7 +204,9 @@ TEST_ORDER_RES = {
|
||||
"vat_id_validated": False
|
||||
},
|
||||
"positions": [TEST_ORDERPOSITION_RES],
|
||||
"downloads": []
|
||||
"downloads": [],
|
||||
"payments": TEST_PAYMENTS_RES,
|
||||
"refunds": TEST_REFUNDS_RES,
|
||||
}
|
||||
|
||||
|
||||
@@ -226,6 +283,252 @@ def test_order_detail(token_client, organizer, event, order, item, taxrule, ques
|
||||
assert len(resp.data['positions'][0]['downloads']) == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_list(token_client, organizer, event, order):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(organizer.slug, event.slug,
|
||||
order.code))
|
||||
assert resp.status_code == 200
|
||||
assert TEST_PAYMENTS_RES == resp.data['results']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_detail(token_client, organizer, event, order):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/payments/1/'.format(organizer.slug, event.slug,
|
||||
order.code))
|
||||
assert resp.status_code == 200
|
||||
assert TEST_PAYMENTS_RES[0] == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_confirm(token_client, organizer, event, order):
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/confirm/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={'force': True})
|
||||
p = order.payments.get(local_id=2)
|
||||
assert resp.status_code == 200
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/confirm/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={'force': True})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_cancel(token_client, organizer, event, order):
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/cancel/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
))
|
||||
p = order.payments.get(local_id=2)
|
||||
assert resp.status_code == 200
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CANCELED
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/cancel/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
))
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch):
|
||||
order.payments.last().confirm()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/refund/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '25.00',
|
||||
'mark_refunded': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Invalid refund amount, only 23.00 are available to refund.']}
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/refund/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '20.00',
|
||||
'mark_refunded': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Partial refund not available for this payment method.']}
|
||||
|
||||
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
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Full refund not available for this payment method.']}
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/refund/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'amount': ['Full refund not available for this payment method.']}
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/1/refund/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': False
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'detail': 'Invalid state of payment.'}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_refund_success(token_client, organizer, event, order, monkeypatch):
|
||||
def charge_retr(*args, **kwargs):
|
||||
def refund_create(amount):
|
||||
pass
|
||||
|
||||
c = MockedCharge()
|
||||
c.refunds.create = refund_create
|
||||
return c
|
||||
|
||||
p1 = order.payments.create(
|
||||
provider='stripe',
|
||||
state='confirmed',
|
||||
amount=Decimal('23.00'),
|
||||
payment_date=order.datetime,
|
||||
info=json.dumps({
|
||||
'id': 'ch_123345345'
|
||||
})
|
||||
)
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/{}/refund/'.format(
|
||||
organizer.slug, event.slug, order.code, p1.local_id
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': False,
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
r = order.refunds.get(local_id=resp.data['local_id'])
|
||||
assert r.provider == "stripe"
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
assert r.source == OrderRefund.REFUND_SOURCE_ADMIN
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_refund_unavailable(token_client, organizer, event, order, monkeypatch):
|
||||
def charge_retr(*args, **kwargs):
|
||||
def refund_create(amount):
|
||||
raise APIConnectionError(message='Foo')
|
||||
|
||||
c = MockedCharge()
|
||||
c.refunds.create = refund_create
|
||||
return c
|
||||
|
||||
p1 = order.payments.create(
|
||||
provider='stripe',
|
||||
state='confirmed',
|
||||
amount=Decimal('23.00'),
|
||||
payment_date=order.datetime,
|
||||
info=json.dumps({
|
||||
'id': 'ch_123345345'
|
||||
})
|
||||
)
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/{}/refund/'.format(
|
||||
organizer.slug, event.slug, order.code, p1.local_id
|
||||
), format='json', data={
|
||||
'amount': '23.00',
|
||||
'mark_refunded': 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.'}
|
||||
r = order.refunds.last()
|
||||
assert r.provider == "stripe"
|
||||
assert r.state == OrderRefund.REFUND_STATE_FAILED
|
||||
assert r.source == OrderRefund.REFUND_SOURCE_ADMIN
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_list(token_client, organizer, event, order):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(organizer.slug, event.slug,
|
||||
order.code))
|
||||
assert resp.status_code == 200
|
||||
assert TEST_REFUNDS_RES == resp.data['results']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_detail(token_client, organizer, event, order):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/refunds/1/'.format(organizer.slug, event.slug,
|
||||
order.code))
|
||||
assert resp.status_code == 200
|
||||
assert TEST_REFUNDS_RES[0] == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_done(token_client, organizer, event, order):
|
||||
r = order.refunds.get(local_id=1)
|
||||
r.state = 'transit'
|
||||
r.save()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/1/done/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
))
|
||||
r = order.refunds.get(local_id=1)
|
||||
assert resp.status_code == 200
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/1/done/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
))
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_process_mark_refunded(token_client, organizer, event, order):
|
||||
p = order.payments.get(local_id=1)
|
||||
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})
|
||||
r = order.refunds.get(local_id=1)
|
||||
assert resp.status_code == 200
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_REFUNDED
|
||||
|
||||
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})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_process_mark_pending(token_client, organizer, event, order):
|
||||
p = order.payments.get(local_id=1)
|
||||
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})
|
||||
r = order.refunds.get(local_id=1)
|
||||
assert resp.status_code == 200
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_cancel(token_client, organizer, event, order):
|
||||
r = order.refunds.get(local_id=1)
|
||||
r.state = 'transit'
|
||||
r.save()
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/1/cancel/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
))
|
||||
r = order.refunds.get(local_id=1)
|
||||
assert resp.status_code == 200
|
||||
assert r.state == OrderRefund.REFUND_STATE_CANCELED
|
||||
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/1/cancel/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
))
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_orderposition_list(token_client, organizer, event, order, item, subevent, subevent2, question):
|
||||
i2 = copy.copy(item)
|
||||
@@ -860,7 +1163,12 @@ def test_order_create(token_client, organizer, event, item, quota, question):
|
||||
assert o.locale == "en"
|
||||
assert o.total == Decimal('23.25')
|
||||
assert o.status == Order.STATUS_PENDING
|
||||
assert o.payment_provider == "banktransfer"
|
||||
|
||||
p = o.payments.first()
|
||||
assert p.provider == "banktransfer"
|
||||
assert p.amount == o.total
|
||||
assert p.state == "created"
|
||||
|
||||
fee = o.fees.first()
|
||||
assert fee.fee_type == "payment"
|
||||
assert fee.value == Decimal('0.25')
|
||||
@@ -953,7 +1261,6 @@ def test_order_create_payment_info_optional(token_client, organizer, event, item
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
assert not o.payment_info == "{}"
|
||||
|
||||
res['payment_info'] = {
|
||||
'foo': {
|
||||
@@ -968,7 +1275,11 @@ def test_order_create_payment_info_optional(token_client, organizer, event, item
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
assert json.loads(o.payment_info) == res['payment_info']
|
||||
|
||||
p = o.payments.first()
|
||||
assert p.provider == "banktransfer"
|
||||
assert p.amount == o.total
|
||||
assert json.loads(p.info) == res['payment_info']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1725,7 +2036,11 @@ def test_order_create_free(token_client, organizer, event, item, quota, question
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
assert o.total == Decimal('0.00')
|
||||
assert o.status == Order.STATUS_PAID
|
||||
assert o.payment_provider == "free"
|
||||
|
||||
p = o.payments.first()
|
||||
assert p.provider == "free"
|
||||
assert p.amount == o.total
|
||||
assert p.state == "confirmed"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1803,3 +2118,76 @@ def test_order_create_paid_generate_invoice(token_client, organizer, event, item
|
||||
assert resp.status_code == 201
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
assert o.invoices.count() == 1
|
||||
|
||||
p = o.payments.first()
|
||||
assert p.provider == "banktransfer"
|
||||
assert p.amount == o.total
|
||||
assert p.state == "confirmed"
|
||||
|
||||
|
||||
REFUND_CREATE_PAYLOAD = {
|
||||
"state": "created",
|
||||
"provider": "manual",
|
||||
"amount": "23.00",
|
||||
"source": "admin",
|
||||
"payment": 2,
|
||||
"info": {
|
||||
"foo": "bar",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_create(token_client, organizer, event, order):
|
||||
res = copy.deepcopy(REFUND_CREATE_PAYLOAD)
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
r = order.refunds.get(local_id=resp.data['local_id'])
|
||||
assert r.provider == "manual"
|
||||
assert r.amount == Decimal("23.00")
|
||||
assert r.state == "created"
|
||||
assert r.source == "admin"
|
||||
assert r.info_data == {"foo": "bar"}
|
||||
assert r.payment.local_id == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_optional_fields(token_client, organizer, event, order):
|
||||
res = copy.deepcopy(REFUND_CREATE_PAYLOAD)
|
||||
del res['info']
|
||||
del res['payment']
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
r = order.refunds.get(local_id=resp.data['local_id'])
|
||||
assert r.provider == "manual"
|
||||
assert r.amount == Decimal("23.00")
|
||||
assert r.state == "created"
|
||||
assert r.source == "admin"
|
||||
|
||||
del res['state']
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_create_invalid_payment(token_client, organizer, event, order):
|
||||
res = copy.deepcopy(REFUND_CREATE_PAYLOAD)
|
||||
res['payment'] = 7
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/refunds/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data=res
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
@@ -73,6 +73,16 @@ event_permission_sub_urls = [
|
||||
('post', 'can_change_orders', 'orders/ABC12/mark_expired/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/mark_canceled/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/extend/', 400),
|
||||
('get', 'can_view_orders', 'orders/ABC12/payments/', 404),
|
||||
('get', 'can_view_orders', 'orders/ABC12/payments/1/', 404),
|
||||
('get', 'can_view_orders', 'orders/ABC12/refunds/', 404),
|
||||
('get', 'can_view_orders', 'orders/ABC12/refunds/1/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/payments/1/confirm/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/payments/1/refund/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/payments/1/cancel/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/refunds/1/cancel/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/refunds/1/process/', 404),
|
||||
('post', 'can_change_orders', 'orders/ABC12/refunds/1/done/', 404),
|
||||
('get', 'can_view_orders', 'checkinlists/', 200),
|
||||
('post', 'can_change_event_settings', 'checkinlists/', 400),
|
||||
('put', 'can_change_event_settings', 'checkinlists/1/', 404),
|
||||
|
||||
@@ -32,7 +32,7 @@ def env():
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer', locale='en'
|
||||
total=0, locale='en'
|
||||
)
|
||||
tr = event.tax_rules.create(rate=Decimal('19.00'))
|
||||
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
|
||||
@@ -275,7 +275,7 @@ def test_invoice_numbers(env):
|
||||
code='BAR', event=event, email='dummy2@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer',
|
||||
total=0,
|
||||
locale='en'
|
||||
)
|
||||
order2.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('0.00'),
|
||||
@@ -322,7 +322,7 @@ def test_invoice_number_prefixes(env):
|
||||
event=event2, email='dummy2@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer',
|
||||
total=0,
|
||||
locale='en'
|
||||
)
|
||||
order2.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('0.00'),
|
||||
|
||||
@@ -16,15 +16,13 @@ from django.utils.timezone import now
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedFile, CartPosition, CheckinList, Event, Item, ItemCategory,
|
||||
ItemVariation, Order, OrderPosition, Organizer, Question, Quota, User,
|
||||
Voucher, WaitingListEntry,
|
||||
ItemVariation, Order, OrderPayment, OrderPosition, OrderRefund, Organizer,
|
||||
Question, Quota, User, Voucher, WaitingListEntry,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.items import SubEventItem, SubEventItemVariation
|
||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
from pretix.base.services.orders import (
|
||||
OrderError, cancel_order, mark_order_paid, perform_order,
|
||||
)
|
||||
from pretix.base.services.orders import OrderError, cancel_order, perform_order
|
||||
|
||||
|
||||
class UserTestCase(TestCase):
|
||||
@@ -600,7 +598,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
def test_paid_in_time(self):
|
||||
self.quota.size = 0
|
||||
self.quota.save()
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_PAID)
|
||||
|
||||
@@ -609,7 +609,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.order.status = Order.STATUS_EXPIRED
|
||||
self.order.expires = now() - timedelta(days=2)
|
||||
self.order.save()
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_PAID)
|
||||
|
||||
@@ -619,7 +621,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.order.expires = now() - timedelta(days=2)
|
||||
self.order.save()
|
||||
with self.assertRaises(Quota.QuotaExceededException):
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_EXPIRED)
|
||||
|
||||
@@ -640,7 +644,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.order.expires = now() - timedelta(days=2)
|
||||
self.order.save()
|
||||
with self.assertRaises(Quota.QuotaExceededException):
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_EXPIRED)
|
||||
self.event.has_subevents = False
|
||||
@@ -652,7 +658,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.order.expires = now() - timedelta(days=2)
|
||||
self.order.save()
|
||||
with self.assertRaises(Quota.QuotaExceededException):
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_EXPIRED)
|
||||
|
||||
@@ -664,7 +672,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.quota.size = 0
|
||||
self.quota.save()
|
||||
with self.assertRaises(Quota.QuotaExceededException):
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertIn(self.order.status, (Order.STATUS_PENDING, Order.STATUS_EXPIRED))
|
||||
|
||||
@@ -672,7 +682,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.event.settings.payment_term_accept_late = True
|
||||
self.order.expires = now() - timedelta(days=2)
|
||||
self.order.save()
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_PAID)
|
||||
|
||||
@@ -683,7 +695,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.order.save()
|
||||
self.quota.size = 0
|
||||
self.quota.save()
|
||||
mark_order_paid(self.order, force=True)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm(force=True)
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_PAID)
|
||||
|
||||
@@ -696,7 +710,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.quota.size = 2
|
||||
self.quota.save()
|
||||
with self.assertRaises(Quota.QuotaExceededException):
|
||||
mark_order_paid(self.order)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm()
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_EXPIRED)
|
||||
|
||||
@@ -707,7 +723,9 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.order.save()
|
||||
self.quota.size = 2
|
||||
self.quota.save()
|
||||
mark_order_paid(self.order, count_waitinglist=False)
|
||||
self.order.payments.create(
|
||||
provider='manual', amount=self.order.total
|
||||
).confirm(count_waitinglist=False)
|
||||
self.order = Order.objects.get(id=self.order.id)
|
||||
self.assertEqual(self.order.status, Order.STATUS_PAID)
|
||||
|
||||
@@ -866,6 +884,144 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
assert p1.secret != p2.secret
|
||||
assert self.order.can_user_cancel is False
|
||||
|
||||
def test_paid_order_underpaid(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
amount=Decimal('46.00'),
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual'
|
||||
)
|
||||
self.order.refunds.create(
|
||||
amount=Decimal('10.00'),
|
||||
state=OrderRefund.REFUND_STATE_DONE,
|
||||
provider='manual'
|
||||
)
|
||||
assert self.order.pending_sum == Decimal('10.00')
|
||||
o = Order.annotate_overpayments(Order.objects.all()).first()
|
||||
assert o.is_underpaid
|
||||
assert not o.is_overpaid
|
||||
assert not o.has_pending_refund
|
||||
assert not o.has_external_refund
|
||||
|
||||
def test_pending_order_underpaid(self):
|
||||
self.order.payments.create(
|
||||
amount=Decimal('46.00'),
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual'
|
||||
)
|
||||
self.order.refunds.create(
|
||||
amount=Decimal('10.00'),
|
||||
state=OrderRefund.REFUND_STATE_DONE,
|
||||
provider='manual'
|
||||
)
|
||||
assert self.order.pending_sum == Decimal('10.00')
|
||||
o = Order.annotate_overpayments(Order.objects.all()).first()
|
||||
assert not o.is_underpaid
|
||||
assert not o.is_overpaid
|
||||
assert not o.has_pending_refund
|
||||
assert not o.has_external_refund
|
||||
|
||||
def test_canceled_order_overpaid(self):
|
||||
self.order.status = Order.STATUS_CANCELED
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
amount=Decimal('46.00'),
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual'
|
||||
)
|
||||
self.order.refunds.create(
|
||||
amount=Decimal('10.00'),
|
||||
state=OrderRefund.REFUND_STATE_DONE,
|
||||
provider='manual'
|
||||
)
|
||||
assert self.order.pending_sum == Decimal('-36.00')
|
||||
o = Order.annotate_overpayments(Order.objects.all()).first()
|
||||
assert not o.is_underpaid
|
||||
assert o.is_overpaid
|
||||
assert not o.has_pending_refund
|
||||
assert not o.has_external_refund
|
||||
|
||||
def test_paid_order_external_refund(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
amount=Decimal('46.00'),
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual'
|
||||
)
|
||||
self.order.refunds.create(
|
||||
amount=Decimal('10.00'),
|
||||
state=OrderRefund.REFUND_STATE_EXTERNAL,
|
||||
provider='manual'
|
||||
)
|
||||
assert self.order.pending_sum == Decimal('0.00')
|
||||
o = Order.annotate_overpayments(Order.objects.all()).first()
|
||||
assert not o.is_underpaid
|
||||
assert not o.is_overpaid
|
||||
assert not o.has_pending_refund
|
||||
assert o.has_external_refund
|
||||
|
||||
def test_pending_order_pending_refund(self):
|
||||
self.order.status = Order.STATUS_REFUNDED
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
amount=Decimal('46.00'),
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual'
|
||||
)
|
||||
self.order.refunds.create(
|
||||
amount=Decimal('46.00'),
|
||||
state=OrderRefund.REFUND_STATE_CREATED,
|
||||
provider='manual'
|
||||
)
|
||||
assert self.order.pending_sum == Decimal('0.00')
|
||||
o = Order.annotate_overpayments(Order.objects.all()).first()
|
||||
assert not o.is_underpaid
|
||||
assert not o.is_overpaid
|
||||
assert o.has_pending_refund
|
||||
assert not o.has_external_refund
|
||||
|
||||
def test_paid_order_overpaid(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
amount=Decimal('66.00'),
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual'
|
||||
)
|
||||
self.order.refunds.create(
|
||||
amount=Decimal('10.00'),
|
||||
state=OrderRefund.REFUND_STATE_DONE,
|
||||
provider='manual'
|
||||
)
|
||||
assert self.order.pending_sum == Decimal('-10.00')
|
||||
o = Order.annotate_overpayments(Order.objects.all()).first()
|
||||
assert not o.is_underpaid
|
||||
assert o.is_overpaid
|
||||
assert not o.has_pending_refund
|
||||
assert not o.has_external_refund
|
||||
|
||||
def test_pending_order_overpaid(self):
|
||||
self.order.status = Order.STATUS_PENDING
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
amount=Decimal('56.00'),
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual'
|
||||
)
|
||||
self.order.refunds.create(
|
||||
amount=Decimal('10.00'),
|
||||
state=OrderRefund.REFUND_STATE_DONE,
|
||||
provider='manual'
|
||||
)
|
||||
assert self.order.pending_sum == Decimal('0.00')
|
||||
o = Order.annotate_overpayments(Order.objects.all()).first()
|
||||
assert not o.is_underpaid
|
||||
assert o.is_overpaid
|
||||
assert not o.has_pending_refund
|
||||
assert not o.has_external_refund
|
||||
|
||||
|
||||
class ItemCategoryTest(TestCase):
|
||||
"""
|
||||
@@ -1130,7 +1286,7 @@ class CheckinListTestCase(TestCase):
|
||||
code='FOO', event=cls.event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal("30"), payment_provider='banktransfer', locale='en'
|
||||
total=Decimal("30"), locale='en'
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=o,
|
||||
@@ -1157,7 +1313,7 @@ class CheckinListTestCase(TestCase):
|
||||
code='FOO', event=cls.event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal("30"), payment_provider='banktransfer', locale='en'
|
||||
total=Decimal("30"), locale='en'
|
||||
)
|
||||
op4 = OrderPosition.objects.create(
|
||||
order=o,
|
||||
|
||||
@@ -27,7 +27,7 @@ def order(event):
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, locale='en',
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('46.00'), payment_provider='banktransfer'
|
||||
total=Decimal('46.00'),
|
||||
)
|
||||
tr19 = event.tax_rules.create(rate=Decimal('19.00'))
|
||||
ticket = Item.objects.create(event=event, name='Early-bird ticket', tax_rule=tr19,
|
||||
|
||||
@@ -13,13 +13,13 @@ from pretix.base.models import (
|
||||
CartPosition, Event, InvoiceAddress, Item, Order, OrderPosition, Organizer,
|
||||
)
|
||||
from pretix.base.models.items import SubEventItem
|
||||
from pretix.base.models.orders import OrderFee
|
||||
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
|
||||
from pretix.base.payment import FreeOrderProvider
|
||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
from pretix.base.services.invoices import generate_invoice
|
||||
from pretix.base.services.orders import (
|
||||
OrderChangeManager, OrderError, _create_order, expire_orders,
|
||||
mark_order_paid, send_download_reminders,
|
||||
send_download_reminders,
|
||||
)
|
||||
|
||||
|
||||
@@ -147,13 +147,13 @@ def test_expiring(event):
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, locale='en',
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer'
|
||||
total=0,
|
||||
)
|
||||
o2 = Order.objects.create(
|
||||
code='FO2', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, locale='en',
|
||||
datetime=now(), expires=now() - timedelta(days=10),
|
||||
total=12, payment_provider='banktransfer'
|
||||
total=12,
|
||||
)
|
||||
generate_invoice(o2)
|
||||
expire_orders(None)
|
||||
@@ -171,14 +171,16 @@ def test_expiring_paid_invoice(event):
|
||||
code='FO2', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, locale='en',
|
||||
datetime=now(), expires=now() - timedelta(days=10),
|
||||
total=12, payment_provider='banktransfer'
|
||||
total=12,
|
||||
)
|
||||
generate_invoice(o2)
|
||||
expire_orders(None)
|
||||
o2 = Order.objects.get(id=o2.id)
|
||||
assert o2.status == Order.STATUS_EXPIRED
|
||||
assert o2.invoices.count() == 2
|
||||
mark_order_paid(o2)
|
||||
o2.payments.create(
|
||||
provider='manual', amount=o2.total
|
||||
).confirm()
|
||||
assert o2.invoices.count() == 3
|
||||
assert o2.invoices.last().is_cancellation is False
|
||||
|
||||
@@ -190,13 +192,13 @@ def test_expiring_auto_disabled(event):
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer'
|
||||
total=0,
|
||||
)
|
||||
o2 = Order.objects.create(
|
||||
code='FO2', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() - timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer'
|
||||
total=0,
|
||||
)
|
||||
expire_orders(None)
|
||||
o1 = Order.objects.get(id=o1.id)
|
||||
@@ -219,7 +221,7 @@ class DownloadReminderTests(TestCase):
|
||||
status=Order.STATUS_PAID, locale='en',
|
||||
datetime=now(),
|
||||
expires=now() + timedelta(days=10),
|
||||
total=Decimal('46.00'), payment_provider='banktransfer'
|
||||
total=Decimal('46.00'),
|
||||
)
|
||||
self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket',
|
||||
default_price=Decimal('23.00'), admission=True)
|
||||
@@ -264,7 +266,10 @@ class OrderChangeManagerTests(TestCase):
|
||||
code='FOO', event=self.event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, locale='en',
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('46.00'), payment_provider='banktransfer'
|
||||
total=Decimal('46.00'),
|
||||
)
|
||||
self.order.payments.create(
|
||||
provider='banktransfer', state=OrderPayment.PAYMENT_STATE_CREATED, amount=self.order.total
|
||||
)
|
||||
self.tr7 = self.event.tax_rules.create(rate=Decimal('7.00'))
|
||||
self.tr19 = self.event.tax_rules.create(rate=Decimal('19.00'))
|
||||
@@ -593,10 +598,9 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.ocm.change_item(self.op1, self.shirt, None)
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.item == self.ticket
|
||||
assert self.op1.item == self.shirt
|
||||
|
||||
def test_change_price_to_free_marked_as_paid(self):
|
||||
self.ocm.change_price(self.op1, Decimal('0.00'))
|
||||
@@ -605,7 +609,7 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.total == 0
|
||||
assert self.order.status == Order.STATUS_PAID
|
||||
assert self.order.payment_provider == 'free'
|
||||
assert self.order.payments.last().provider == 'free'
|
||||
|
||||
def test_change_paid_same_price(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
@@ -620,12 +624,26 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.ocm.change_price(self.op1, Decimal('5.00'))
|
||||
with self.assertRaises(OrderError):
|
||||
self.ocm.commit()
|
||||
self.ocm.commit()
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.total == 46
|
||||
assert self.order.total == Decimal('28.00')
|
||||
assert self.order.status == Order.STATUS_PAID
|
||||
|
||||
def test_change_paid_to_pending(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
provider='manual',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
amount=self.order.total,
|
||||
)
|
||||
self.ocm.change_price(self.op1, Decimal('25.00'))
|
||||
self.ocm.commit()
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.total == Decimal('48.00')
|
||||
assert self.order.pending_sum == Decimal('2.00')
|
||||
assert self.order.status == Order.STATUS_PENDING
|
||||
|
||||
def test_add_item_quota_required(self):
|
||||
self.quota.delete()
|
||||
with self.assertRaises(OrderError):
|
||||
@@ -775,10 +793,10 @@ class OrderChangeManagerTests(TestCase):
|
||||
assert fee.tax_rate == Decimal('19.00')
|
||||
assert fee.tax_value == Decimal('0.05')
|
||||
|
||||
self.ocm = OrderChangeManager(self.order, None)
|
||||
ia = self._enable_reverse_charge()
|
||||
self.ocm.recalculate_taxes()
|
||||
self.ocm.commit()
|
||||
self.ocm = OrderChangeManager(self.order, None)
|
||||
ops = list(self.order.positions.all())
|
||||
for op in ops:
|
||||
assert op.price == Decimal('21.50')
|
||||
@@ -794,6 +812,7 @@ class OrderChangeManagerTests(TestCase):
|
||||
ia.vat_id_validated = False
|
||||
ia.save()
|
||||
|
||||
self.ocm = OrderChangeManager(self.order, None)
|
||||
self.ocm.recalculate_taxes()
|
||||
self.ocm.commit()
|
||||
ops = list(self.order.positions.all())
|
||||
@@ -870,6 +889,11 @@ class OrderChangeManagerTests(TestCase):
|
||||
def test_split_paid_no_payment_fees(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
provider='manual',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
amount=self.order.total,
|
||||
)
|
||||
|
||||
# Split
|
||||
self.ocm.split(self.op2)
|
||||
@@ -880,6 +904,11 @@ class OrderChangeManagerTests(TestCase):
|
||||
# First order
|
||||
assert self.order.total == Decimal('23.00')
|
||||
assert not self.order.fees.exists()
|
||||
assert self.order.pending_sum == Decimal('0.00')
|
||||
r = self.order.refunds.last()
|
||||
assert r.provider == 'offsetting'
|
||||
assert r.amount == Decimal('23.00')
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
|
||||
# New order
|
||||
assert self.op2.order != self.order
|
||||
@@ -888,6 +917,11 @@ class OrderChangeManagerTests(TestCase):
|
||||
assert o2.status == Order.STATUS_PAID
|
||||
assert o2.positions.count() == 1
|
||||
assert o2.fees.count() == 0
|
||||
assert o2.pending_sum == Decimal('0.00')
|
||||
p = o2.payments.last()
|
||||
assert p.provider == 'offsetting'
|
||||
assert p.amount == Decimal('23.00')
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
|
||||
def test_split_invoice_address(self):
|
||||
ia = InvoiceAddress.objects.create(
|
||||
@@ -1026,6 +1060,9 @@ class OrderChangeManagerTests(TestCase):
|
||||
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
payment = self.order.payments.first()
|
||||
payment.state = OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
payment.save()
|
||||
|
||||
# Split
|
||||
self.ocm.split(self.op2)
|
||||
|
||||
@@ -150,7 +150,7 @@ def test_availability_date_order_relative_subevents(event):
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=Decimal('46.00'), payment_provider='dummtest'
|
||||
total=Decimal('46.00'),
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=order, item=ticket, variation=None, subevent=se1,
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import (
|
||||
CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, Order,
|
||||
OrderPosition, Organizer, QuestionAnswer,
|
||||
OrderPayment, OrderPosition, Organizer, QuestionAnswer,
|
||||
)
|
||||
from pretix.base.services.invoices import generate_invoice, invoice_pdf_task
|
||||
from pretix.base.services.tickets import generate, generate_order
|
||||
@@ -45,7 +45,7 @@ def order(event, item):
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
event.settings.set('attendee_names_asked', True)
|
||||
event.settings.set('locales', ['en', 'de'])
|
||||
@@ -295,12 +295,12 @@ def test_cached_tickets(event, order):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_info_shredder(event, order):
|
||||
order.payment_info = json.dumps({
|
||||
order.payments.create(info=json.dumps({
|
||||
'reference': 'Verwendungszweck 1',
|
||||
'date': '2018-05-01',
|
||||
'payer': 'Hans',
|
||||
'trans_id': 12
|
||||
})
|
||||
}), provider='banktransfer', amount=order.total, state=OrderPayment.PAYMENT_STATE_PENDING)
|
||||
order.save()
|
||||
|
||||
s = PaymentInfoShredder(event)
|
||||
@@ -308,7 +308,7 @@ def test_payment_info_shredder(event, order):
|
||||
s.shred_data()
|
||||
|
||||
order.refresh_from_db()
|
||||
assert json.loads(order.payment_info) == {
|
||||
assert order.payments.first().info_data == {
|
||||
'_shredded': True,
|
||||
'reference': '█',
|
||||
'date': '2018-05-01',
|
||||
|
||||
@@ -38,7 +38,7 @@ def dashboard_env():
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=33, payment_provider='banktransfer', locale='en'
|
||||
total=33, locale='en'
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=order_paid,
|
||||
@@ -70,7 +70,7 @@ def test_dashboard_pending_not_count(dashboard_env):
|
||||
code='FOO', event=dashboard_env[0], email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=order_pending,
|
||||
@@ -122,25 +122,25 @@ def checkin_list_env():
|
||||
code='PENDING', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
order_a1 = Order.objects.create(
|
||||
code='A1', event=event, email='a1dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=33, payment_provider='banktransfer', locale='en'
|
||||
total=33, locale='en'
|
||||
)
|
||||
order_a2 = Order.objects.create(
|
||||
code='A2', event=event, email='a2dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
order_a3 = Order.objects.create(
|
||||
code='A3', event=event, email='a3dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
|
||||
# order position
|
||||
@@ -298,19 +298,19 @@ def checkin_list_with_addon_env():
|
||||
code='PENDING', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
order_a1 = Order.objects.create(
|
||||
code='A1', event=event, email='a1dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=33, payment_provider='banktransfer', locale='en'
|
||||
total=33, locale='en'
|
||||
)
|
||||
order_a2 = Order.objects.create(
|
||||
code='A2', event=event, email='a2dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer', locale='en'
|
||||
total=23, locale='en'
|
||||
)
|
||||
|
||||
# order position
|
||||
|
||||
@@ -838,7 +838,7 @@ class SubEventsTest(SoupTest):
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=o,
|
||||
@@ -1180,7 +1180,7 @@ class SubEventsTest(SoupTest):
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=o,
|
||||
@@ -1266,7 +1266,7 @@ class EventDeletionTest(SoupTest):
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now(),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
self.post_doc('/control/event/ccc/30c3/delete/', {
|
||||
'user_pw': 'dummy',
|
||||
|
||||
@@ -185,7 +185,7 @@ class QuestionsTest(ItemFormTest):
|
||||
o = Order.objects.create(code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, datetime=now(),
|
||||
expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en')
|
||||
total=14, locale='en')
|
||||
op = OrderPosition.objects.create(order=o, item=item1, variation=None, price=Decimal("14"),
|
||||
attendee_name="Peter")
|
||||
op.answers.create(question=c, answer='42')
|
||||
@@ -407,7 +407,7 @@ class ItemsTest(ItemFormTest):
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
OrderPosition.objects.create(
|
||||
order=o,
|
||||
|
||||
@@ -3,15 +3,18 @@ from decimal import Decimal
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from bs4 import BeautifulSoup
|
||||
from django.core import mail
|
||||
from django.utils.timezone import now
|
||||
from django_countries.fields import Country
|
||||
from tests.base import SoupTest
|
||||
from tests.plugins.stripe.test_provider import MockedCharge
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, InvoiceAddress, Item, Order, OrderPosition, Organizer, Question,
|
||||
QuestionAnswer, Quota, Team, User,
|
||||
Event, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
|
||||
OrderRefund, Organizer, Question, QuestionAnswer, Quota, Team, User,
|
||||
)
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.services.invoices import (
|
||||
generate_cancellation, generate_invoice,
|
||||
)
|
||||
@@ -22,7 +25,7 @@ def env():
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now(), plugins='pretix.plugins.banktransfer,tests.testdummy'
|
||||
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.stripe,tests.testdummy'
|
||||
)
|
||||
event.settings.set('ticketoutput_testdummy__enabled', True)
|
||||
user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
||||
@@ -33,7 +36,10 @@ def env():
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
o.payments.create(
|
||||
amount=o.total, provider='banktransfer', state=OrderPayment.PAYMENT_STATE_PENDING
|
||||
)
|
||||
ticket = Item.objects.create(event=event, name='Early-bird ticket',
|
||||
category=None, default_price=23,
|
||||
@@ -212,7 +218,7 @@ def test_order_transition_to_paid_expired_quota_left(client, env):
|
||||
|
||||
(Order.STATUS_PAID, Order.STATUS_PENDING, True),
|
||||
(Order.STATUS_PAID, Order.STATUS_CANCELED, False),
|
||||
(Order.STATUS_PAID, Order.STATUS_REFUNDED, True),
|
||||
(Order.STATUS_PAID, Order.STATUS_REFUNDED, False),
|
||||
(Order.STATUS_PAID, Order.STATUS_EXPIRED, False),
|
||||
|
||||
(Order.STATUS_PENDING, Order.STATUS_CANCELED, True),
|
||||
@@ -688,7 +694,7 @@ class OrderChangeTests(SoupTest):
|
||||
code='FOO', event=self.event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('46.00'), payment_provider='banktransfer'
|
||||
total=Decimal('46.00'),
|
||||
)
|
||||
self.tr7 = self.event.tax_rules.create(rate=Decimal('7.00'))
|
||||
self.tr19 = self.event.tax_rules.create(rate=Decimal('19.00'))
|
||||
@@ -944,3 +950,530 @@ def test_check_vatid_unavailable(client, env):
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
ia.refresh_from_db()
|
||||
assert not ia.vat_id_validated
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cancel_payment(client, env):
|
||||
p = env[2].payments.last()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/cancel'.format(p.pk), {}, follow=True)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CANCELED
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/cancel'.format(p.pk), {}, follow=True)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cancel_refund(client, env):
|
||||
r = env[2].refunds.create(
|
||||
provider='stripe',
|
||||
state='transit',
|
||||
source='admin',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/cancel'.format(r.pk), {}, follow=True)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
r.refresh_from_db()
|
||||
assert r.state == OrderRefund.REFUND_STATE_CANCELED
|
||||
r.state = OrderRefund.REFUND_STATE_DONE
|
||||
r.save()
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/cancel'.format(r.pk), {}, follow=True)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
r.refresh_from_db()
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_process_refund(client, env):
|
||||
r = env[2].refunds.create(
|
||||
provider='stripe',
|
||||
state='external',
|
||||
source='external',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
r.refresh_from_db()
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_process_refund_invalid_state(client, env):
|
||||
r = env[2].refunds.create(
|
||||
provider='stripe',
|
||||
state='canceled',
|
||||
source='external',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
r.refresh_from_db()
|
||||
assert r.state == OrderRefund.REFUND_STATE_CANCELED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_process_refund_mark_refunded(client, env):
|
||||
r = env[2].refunds.create(
|
||||
provider='stripe',
|
||||
state='external',
|
||||
source='external',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {'action': 'r'},
|
||||
follow=True)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
r.refresh_from_db()
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_REFUNDED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_done_refund(client, env):
|
||||
r = env[2].refunds.create(
|
||||
provider='stripe',
|
||||
state='transit',
|
||||
source='admin',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/done'.format(r.pk), {}, follow=True)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
r.refresh_from_db()
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_done_refund_invalid_state(client, env):
|
||||
r = env[2].refunds.create(
|
||||
provider='stripe',
|
||||
state='external',
|
||||
source='external',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/done'.format(r.pk), {}, follow=True)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
r.refresh_from_db()
|
||||
assert r.state == OrderRefund.REFUND_STATE_EXTERNAL
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_confirm_payment(client, env):
|
||||
p = env[2].payments.last()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {}, follow=True)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_confirm_payment_invalid_state(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.state = OrderPayment.PAYMENT_STATE_FAILED
|
||||
p.save()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {}, follow=True)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_FAILED
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_confirm_payment_partal_amount(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.amount -= Decimal(5.00)
|
||||
p.save()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {}, follow=True)
|
||||
assert 'alert-success' in response.rendered_content
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_paid_order_fully_mark_as_refunded(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.get('/control/event/dummy/dummy/orders/FOO/refund')
|
||||
doc = BeautifulSoup(response.content, "lxml")
|
||||
assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00"
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '14.00',
|
||||
'start-mode': 'full',
|
||||
'start-action': 'mark_refunded'
|
||||
}, follow=True)
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '14.00',
|
||||
'start-mode': 'full',
|
||||
'start-action': 'mark_refunded',
|
||||
'refund-manual': '14.00',
|
||||
'manual_state': 'done',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
r = env[2].refunds.last()
|
||||
assert r.provider == "manual"
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
assert r.amount == Decimal('14.00')
|
||||
assert env[2].status == Order.STATUS_REFUNDED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_paid_order_fully_mark_as_pending(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.get('/control/event/dummy/dummy/orders/FOO/refund')
|
||||
doc = BeautifulSoup(response.content, "lxml")
|
||||
assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00"
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '14.00',
|
||||
'start-mode': 'full',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-manual': '14.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
r = env[2].refunds.last()
|
||||
assert r.provider == "manual"
|
||||
assert r.state == OrderRefund.REFUND_STATE_CREATED
|
||||
assert r.amount == Decimal('14.00')
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_paid_order_partially_mark_as_pending(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.get('/control/event/dummy/dummy/orders/FOO/refund')
|
||||
doc = BeautifulSoup(response.content, "lxml")
|
||||
assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00"
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending'
|
||||
}, follow=True)
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-manual': '7.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
r = env[2].refunds.last()
|
||||
assert r.provider == "manual"
|
||||
assert r.state == OrderRefund.REFUND_STATE_CREATED
|
||||
assert r.amount == Decimal('7.00')
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_propose_lower_payment(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.amount = Decimal('8.00')
|
||||
p.confirm()
|
||||
p2 = env[2].payments.create(
|
||||
amount=Decimal('6.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
client.get('/control/event/dummy/dummy/orders/FOO/refund')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending'
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content, "lxml")
|
||||
assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '6.00'
|
||||
assert doc.select("input[name=refund-manual]".format(p2.pk))[0]['value'] == '1.00'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_propose_equal_payment(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.amount = Decimal('7.00')
|
||||
p.confirm()
|
||||
p2 = env[2].payments.create(
|
||||
amount=Decimal('7.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
client.get('/control/event/dummy/dummy/orders/FOO/refund')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending'
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content, "lxml")
|
||||
assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '7.00'
|
||||
assert doc.select("input[name=refund-manual]".format(p2.pk))[0]['value'] == '0.00'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_propose_higher_payment(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.amount = Decimal('6.00')
|
||||
p.confirm()
|
||||
p2 = env[2].payments.create(
|
||||
amount=Decimal('8.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
client.get('/control/event/dummy/dummy/orders/FOO/refund')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending'
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content, "lxml")
|
||||
assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '7.00'
|
||||
assert doc.select("input[name=refund-manual]".format(p2.pk))[0]['value'] == '0.00'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_amount_does_not_match_or_invalid(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-manual': '4.00',
|
||||
'refund-{}'.format(p.pk): '4.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
assert b'alert-danger' in resp.content
|
||||
assert b'do not match the' in resp.content
|
||||
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '15.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-manual': '0.00',
|
||||
'refund-{}'.format(p.pk): '15.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
assert b'alert-danger' in resp.content
|
||||
assert b'The refund amount needs to be positive' in resp.content
|
||||
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-manual': '-3.00',
|
||||
'refund-{}'.format(p.pk): '10.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
assert b'alert-danger' in resp.content
|
||||
assert b'do not match the' in resp.content
|
||||
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-manual': 'AA',
|
||||
'refund-{}'.format(p.pk): '10.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
assert b'alert-danger' in resp.content
|
||||
assert b'invalid number' in resp.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_paid_order_automatically_failed(client, env, monkeypatch):
|
||||
p = env[2].payments.last()
|
||||
p.provider = 'stripe'
|
||||
p.info_data = {
|
||||
'id': 'foo'
|
||||
}
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
def charge_retr(*args, **kwargs):
|
||||
def refund_create(amount):
|
||||
raise PaymentException('This failed.')
|
||||
|
||||
c = MockedCharge()
|
||||
c.refunds.create = refund_create
|
||||
return c
|
||||
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
|
||||
|
||||
r = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-{}'.format(p.pk): '7.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
assert b'This failed.' in r.content
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
r = env[2].refunds.last()
|
||||
assert r.provider == "stripe"
|
||||
assert r.state == OrderRefund.REFUND_STATE_FAILED
|
||||
assert r.amount == Decimal('7.00')
|
||||
assert env[2].status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_paid_order_automatically(client, env, monkeypatch):
|
||||
p = env[2].payments.last()
|
||||
p.provider = 'stripe'
|
||||
p.info_data = {
|
||||
'id': 'foo'
|
||||
}
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
def charge_retr(*args, **kwargs):
|
||||
def refund_create(amount):
|
||||
pass
|
||||
|
||||
c = MockedCharge()
|
||||
c.refunds.create = refund_create
|
||||
return c
|
||||
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
|
||||
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '7.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-{}'.format(p.pk): '7.00',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
r = env[2].refunds.last()
|
||||
assert r.provider == "stripe"
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
assert r.amount == Decimal('7.00')
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_paid_order_offsetting_to_unknown(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
r = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '5.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-offsetting': '5.00',
|
||||
'order-offsetting': 'BAZ',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
assert b'alert-danger' in r.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_paid_order_offsetting(client, env):
|
||||
p = env[2].payments.last()
|
||||
p.confirm()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
o = Order.objects.create(
|
||||
code='BAZ', event=env[0], email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=5, locale='en'
|
||||
)
|
||||
|
||||
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
|
||||
'start-partial_amount': '5.00',
|
||||
'start-mode': 'partial',
|
||||
'start-action': 'mark_pending',
|
||||
'refund-offsetting': '5.00',
|
||||
'order-offsetting': 'BAZ',
|
||||
'manual_state': 'pending',
|
||||
'perform': 'on'
|
||||
}, follow=True)
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
env[2].refresh_from_db()
|
||||
r = env[2].refunds.last()
|
||||
assert r.provider == "offsetting"
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
assert r.amount == Decimal('5.00')
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
o.refresh_from_db()
|
||||
assert o.status == Order.STATUS_PAID
|
||||
p2 = o.payments.first()
|
||||
assert p2.provider == "offsetting"
|
||||
assert p2.amount == Decimal('5.00')
|
||||
assert p2.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_refund_list(client, env):
|
||||
env[2].refunds.create(
|
||||
provider='banktransfer',
|
||||
state='done',
|
||||
source='admin',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
env[2].refunds.create(
|
||||
provider='manual',
|
||||
state='created',
|
||||
source='admin',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=now(),
|
||||
)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.get('/control/event/dummy/dummy/orders/refunds/')
|
||||
assert 'R-1' not in response.rendered_content
|
||||
assert 'R-2' in response.rendered_content
|
||||
response = client.get('/control/event/dummy/dummy/orders/refunds/?status=all')
|
||||
assert 'R-1' in response.rendered_content
|
||||
assert 'R-2' in response.rendered_content
|
||||
response = client.get('/control/event/dummy/dummy/orders/refunds/?status=created')
|
||||
assert 'R-1' not in response.rendered_content
|
||||
assert 'R-2' in response.rendered_content
|
||||
response = client.get('/control/event/dummy/dummy/orders/refunds/?status=done')
|
||||
assert 'R-1' in response.rendered_content
|
||||
assert 'R-2' not in response.rendered_content
|
||||
response = client.get('/control/event/dummy/dummy/orders/refunds/?status=all&provider=manual')
|
||||
assert 'R-1' not in response.rendered_content
|
||||
assert 'R-2' in response.rendered_content
|
||||
response = client.get('/control/event/dummy/dummy/orders/refunds/?status=all&provider=banktransfer')
|
||||
assert 'R-1' in response.rendered_content
|
||||
assert 'R-2' not in response.rendered_content
|
||||
|
||||
@@ -18,7 +18,7 @@ def env():
|
||||
code='FOO', event=event,
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer'
|
||||
total=0,
|
||||
)
|
||||
Team.objects.create(pk=1, organizer=o)
|
||||
return event, user, o
|
||||
@@ -103,6 +103,12 @@ event_urls = [
|
||||
"orders/ABC/comment",
|
||||
"orders/ABC/locale",
|
||||
"orders/ABC/checkvatid",
|
||||
"orders/ABC/payments/1/cancel",
|
||||
"orders/ABC/payments/1/confirm",
|
||||
"orders/ABC/refund",
|
||||
"orders/ABC/refunds/1/cancel",
|
||||
"orders/ABC/refunds/1/process",
|
||||
"orders/ABC/refunds/1/done",
|
||||
"orders/ABC/",
|
||||
"orders/",
|
||||
"checkinlists/",
|
||||
|
||||
@@ -28,7 +28,7 @@ class OrderSearchTest(SoupTest):
|
||||
code='FO1A', event=self.event1, email='dummy1@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
InvoiceAddress.objects.create(order=o1, company="Test Ltd.", name="Peter Miller")
|
||||
ticket1 = Item.objects.create(event=self.event1, name='Early-bird ticket',
|
||||
@@ -47,7 +47,7 @@ class OrderSearchTest(SoupTest):
|
||||
code='FO2', event=self.event2, email='dummy2@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
ticket2 = Item.objects.create(event=self.event1, name='Early-bird ticket',
|
||||
category=None, default_price=23,
|
||||
|
||||
@@ -29,7 +29,7 @@ class EventShredderTest(SoupTest):
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now(),
|
||||
total=14, payment_provider='banktransfer', locale='en'
|
||||
total=14, locale='en'
|
||||
)
|
||||
|
||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
@@ -37,7 +37,7 @@ class EventShredderTest(SoupTest):
|
||||
def test_shred_simple(self):
|
||||
doc = self.get_doc('/control/event/%s/%s/shredder/' % (self.orga1.slug, self.event1.slug))
|
||||
assert doc.select("input[value=order_emails]")
|
||||
assert doc.select("input[value=stripe_logs]")
|
||||
assert doc.select("input[value=invoices]")
|
||||
doc = self.post_doc('/control/event/%s/%s/shredder/export' % (self.orga1.slug, self.event1.slug), {
|
||||
'shredder': 'order_emails'
|
||||
})
|
||||
@@ -67,7 +67,7 @@ class EventShredderTest(SoupTest):
|
||||
def test_shred_password_wrong(self):
|
||||
doc = self.get_doc('/control/event/%s/%s/shredder/' % (self.orga1.slug, self.event1.slug))
|
||||
assert doc.select("input[value=order_emails]")
|
||||
assert doc.select("input[value=stripe_logs]")
|
||||
assert doc.select("input[value=invoices]")
|
||||
doc = self.post_doc('/control/event/%s/%s/shredder/export' % (self.orga1.slug, self.event1.slug), {
|
||||
'shredder': 'order_emails'
|
||||
})
|
||||
@@ -97,7 +97,7 @@ class EventShredderTest(SoupTest):
|
||||
def test_shred_confirm_code_wrong(self):
|
||||
doc = self.get_doc('/control/event/%s/%s/shredder/' % (self.orga1.slug, self.event1.slug))
|
||||
assert doc.select("input[value=order_emails]")
|
||||
assert doc.select("input[value=stripe_logs]")
|
||||
assert doc.select("input[value=invoices]")
|
||||
doc = self.post_doc('/control/event/%s/%s/shredder/export' % (self.orga1.slug, self.event1.slug), {
|
||||
'shredder': 'order_emails'
|
||||
})
|
||||
@@ -137,7 +137,7 @@ class EventShredderTest(SoupTest):
|
||||
def test_shred_something_happened(self):
|
||||
doc = self.get_doc('/control/event/%s/%s/shredder/' % (self.orga1.slug, self.event1.slug))
|
||||
assert doc.select("input[value=order_emails]")
|
||||
assert doc.select("input[value=stripe_logs]")
|
||||
assert doc.select("input[value=invoices]")
|
||||
doc = self.post_doc('/control/event/%s/%s/shredder/export' % (self.orga1.slug, self.event1.slug), {
|
||||
'shredder': 'order_emails'
|
||||
})
|
||||
|
||||
@@ -83,7 +83,7 @@ class TaxRateFormTest(SoupTest):
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=14, payment_provider='banktransfer', locale='en',
|
||||
total=14, locale='en',
|
||||
)
|
||||
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
|
||||
tax_value=Decimal('0.05'), tax_rule=tr)
|
||||
@@ -101,7 +101,7 @@ class TaxRateFormTest(SoupTest):
|
||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
||||
total=12, payment_provider='banktransfer', locale='en'
|
||||
total=12, locale='en'
|
||||
)
|
||||
o.positions.create(
|
||||
item=i, price=12, tax_rule=tr, tax_rate=19, tax_value=12 - 12 / 1.19
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
@@ -34,8 +35,22 @@ def order(item):
|
||||
o = Order.objects.create(event=item.event, status=Order.STATUS_PENDING,
|
||||
expires=now() + datetime.timedelta(hours=1),
|
||||
total=13, code='DUMMY', email='dummy@dummy.test',
|
||||
datetime=now(), payment_provider='banktransfer')
|
||||
datetime=now())
|
||||
OrderPosition.objects.create(order=o, item=item, price=13)
|
||||
p1 = o.payments.create(
|
||||
provider='stripe',
|
||||
state='refunded',
|
||||
amount=Decimal('23.00'),
|
||||
payment_date=o.datetime,
|
||||
)
|
||||
o.refunds.create(
|
||||
provider='stripe',
|
||||
state='done',
|
||||
source='admin',
|
||||
amount=Decimal('23.00'),
|
||||
execution_date=o.datetime,
|
||||
payment=p1,
|
||||
)
|
||||
return o
|
||||
|
||||
|
||||
@@ -131,6 +146,12 @@ def logged_in_client(client, event):
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/comment', 405),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/change', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/locale', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/payments/{payment}/cancel', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/payments/{payment}/confirm', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/refund', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/refunds/{refund}/cancel', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/refunds/{refund}/process', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/refunds/{refund}/done', 200),
|
||||
('/control/event/{orga}/{event}/orders/{order_code}/', 200),
|
||||
('/control/event/{orga}/{event}/orders/overview/', 200),
|
||||
('/control/event/{orga}/{event}/orders/export/', 200),
|
||||
@@ -141,6 +162,8 @@ def logged_in_client(client, event):
|
||||
])
|
||||
@pytest.mark.django_db
|
||||
def test_one_view(logged_in_client, url, expected, event, item, item_category, order, question, quota, voucher):
|
||||
payment = order.payments.first()
|
||||
refund = order.refunds.first()
|
||||
url = url.format(
|
||||
event=event.slug, orga=event.organizer.slug,
|
||||
category=item_category.pk,
|
||||
@@ -149,6 +172,8 @@ def test_one_view(logged_in_client, url, expected, event, item, item_category, o
|
||||
question=question.pk,
|
||||
quota=quota.pk,
|
||||
voucher=voucher.pk,
|
||||
payment=payment.pk,
|
||||
refund=refund.pk
|
||||
)
|
||||
response = logged_in_client.get(url)
|
||||
assert response.status_code == expected
|
||||
|
||||
@@ -23,7 +23,7 @@ def env():
|
||||
code='FOOBAR', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('13.37'), payment_provider='banktransfer'
|
||||
total=Decimal('13.37'),
|
||||
)
|
||||
shirt = Item.objects.create(event=event, name='T-Shirt', default_price=12)
|
||||
shirt_red = ItemVariation.objects.create(item=shirt, default_price=14, value="Red")
|
||||
|
||||
@@ -25,13 +25,13 @@ def env():
|
||||
code='1Z3AS', event=event,
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer'
|
||||
total=23,
|
||||
)
|
||||
o2 = Order.objects.create(
|
||||
code='6789Z', event=event,
|
||||
status=Order.STATUS_CANCELED,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer'
|
||||
total=23,
|
||||
)
|
||||
quota = Quota.objects.create(name="Test", size=2, event=event)
|
||||
item1 = Item.objects.create(event=event, name="Ticket", default_price=23)
|
||||
@@ -56,23 +56,6 @@ def test_discard(env, client):
|
||||
assert trans.payer == ''
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_accept_wrong_amount(env, client):
|
||||
job = BankImportJob.objects.create(event=env[0])
|
||||
trans = BankTransaction.objects.create(event=env[0], import_job=job, payer='Foo',
|
||||
state=BankTransaction.STATE_INVALID,
|
||||
amount=12, date='unknown', order=env[2])
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
r = json.loads(client.post('/control/event/{}/{}/banktransfer/action/'.format(env[0].organizer.slug, env[0].slug), {
|
||||
'action_{}'.format(trans.pk): 'accept',
|
||||
}).content.decode('utf-8'))
|
||||
assert r['status'] == 'ok'
|
||||
trans.refresh_from_db()
|
||||
assert trans.state == BankTransaction.STATE_VALID
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_assign_order(env, client):
|
||||
job = BankImportJob.objects.create(event=env[0])
|
||||
|
||||
@@ -28,13 +28,13 @@ def env():
|
||||
code='1Z3AS', event=event,
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer'
|
||||
total=23
|
||||
)
|
||||
o2 = Order.objects.create(
|
||||
code='6789Z', event=event,
|
||||
status=Order.STATUS_CANCELED,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer'
|
||||
total=23
|
||||
)
|
||||
quota = Quota.objects.create(name="Test", size=2, event=event)
|
||||
item1 = Item.objects.create(event=event, name="Ticket", default_price=23)
|
||||
|
||||
@@ -7,7 +7,8 @@ from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Item, Order, OrderPosition, Organizer, Quota, Team, User,
|
||||
Event, Item, Order, OrderPayment, OrderPosition, Organizer, Quota, Team,
|
||||
User,
|
||||
)
|
||||
from pretix.plugins.banktransfer.models import BankImportJob
|
||||
from pretix.plugins.banktransfer.tasks import process_banktransfers
|
||||
@@ -28,19 +29,19 @@ def env():
|
||||
code='1Z3AS', event=event,
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer'
|
||||
total=23
|
||||
)
|
||||
o2 = Order.objects.create(
|
||||
code='6789Z', event=event,
|
||||
status=Order.STATUS_CANCELED,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer'
|
||||
total=23
|
||||
)
|
||||
Order.objects.create(
|
||||
code='GS89Z', event=event,
|
||||
status=Order.STATUS_CANCELED,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=23, payment_provider='banktransfer'
|
||||
total=23
|
||||
)
|
||||
quota = Quota.objects.create(name="Test", size=2, event=event)
|
||||
item1 = Item.objects.create(event=event, name="Ticket", default_price=23)
|
||||
@@ -110,7 +111,43 @@ def test_mark_paid(env, job):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_check_amount(env, job):
|
||||
def test_underpaid(env, job):
|
||||
process_banktransfers(job, [{
|
||||
'payer': 'Karla Kundin',
|
||||
'reference': 'Bestellung DUMMY1Z3AS',
|
||||
'date': '2016-01-26',
|
||||
'amount': '22.50'
|
||||
}])
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
p = env[2].payments.last()
|
||||
assert p.amount == Decimal('22.50')
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
assert env[2].pending_sum == Decimal('0.50')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_in_parts(env, job):
|
||||
process_banktransfers(job, [{
|
||||
'payer': 'Karla Kundin',
|
||||
'reference': 'Bestellung DUMMY1Z3AS',
|
||||
'date': '2016-01-26',
|
||||
'amount': '10.00'
|
||||
}])
|
||||
process_banktransfers(job, [{
|
||||
'payer': 'Karla Kundin',
|
||||
'reference': 'Bestellung DUMMY1Z3AS',
|
||||
'date': '2016-01-26',
|
||||
'amount': '13.00'
|
||||
}])
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PAID
|
||||
assert env[2].payments.count() == 2
|
||||
assert env[2].pending_sum == Decimal('0.00')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_overpaid(env, job):
|
||||
process_banktransfers(job, [{
|
||||
'payer': 'Karla Kundin',
|
||||
'reference': 'Bestellung DUMMY1Z3AS',
|
||||
@@ -118,7 +155,11 @@ def test_check_amount(env, job):
|
||||
'amount': '23.50'
|
||||
}])
|
||||
env[2].refresh_from_db()
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
assert env[2].status == Order.STATUS_PAID
|
||||
p = env[2].payments.last()
|
||||
assert p.amount == Decimal('23.50')
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
assert env[2].pending_sum == Decimal('-0.50')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Order, Organizer, RequiredAction, Team, User,
|
||||
Event, Order, OrderPayment, OrderRefund, Organizer, Team, User,
|
||||
)
|
||||
from pretix.plugins.paypal.models import ReferencedPayPalObject
|
||||
|
||||
@@ -26,8 +26,13 @@ def env():
|
||||
code='FOOBAR', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('13.37'), payment_provider='paypal',
|
||||
payment_info=json.dumps({
|
||||
total=Decimal('13.37'),
|
||||
)
|
||||
o1.payments.create(
|
||||
amount=o1.total,
|
||||
provider='paypal',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
info=json.dumps({
|
||||
"id": "PAY-5YK922393D847794YKER7MUI",
|
||||
"create_time": "2013-02-19T22:01:53Z",
|
||||
"update_time": "2013-02-19T22:01:55Z",
|
||||
@@ -150,6 +155,33 @@ def get_test_charge(order: Order):
|
||||
}
|
||||
|
||||
|
||||
def get_test_refund(order: Order):
|
||||
return {
|
||||
'refund_from_received_amount': {'value': '13.30', 'currency': 'EUR'},
|
||||
'amount': {'total': '13.37', 'currency': 'EUR'},
|
||||
'sale_id': '1G495778AR8401726',
|
||||
'update_time': '2018-07-24T07:50:07Z',
|
||||
'total_refunded_amount': {'value': '13.37', 'currency': 'EUR'},
|
||||
'refund_reason_code': 'REFUND',
|
||||
'invoice_number': 'Test',
|
||||
'parent_payment': 'PAY-0UB50445HE155450FLNLNMUY',
|
||||
'state': 'completed',
|
||||
'create_time': '2018-07-24T07:50:07Z',
|
||||
'refund_from_transaction_fee': {'value': '0.07', 'currency': 'EUR'},
|
||||
'id': '93M41501U3542574L',
|
||||
'refund_to_payer': {'value': '13.37', 'currency': 'EUR'},
|
||||
'links': [
|
||||
{'method': 'GET', 'rel': 'self',
|
||||
'href': 'https://api.sandbox.paypal.com/v1/payments/refund/93M41501U3542574L'},
|
||||
{'method': 'GET',
|
||||
'rel': 'parent_payment',
|
||||
'href': 'https://api.sandbox.paypal.com/v1/payments/payment/PAY-0UB50445HE155450FLNLNMUY'},
|
||||
{'method': 'GET', 'rel': 'sale',
|
||||
'href': 'https://api.sandbox.paypal.com/v1/payments/sale/1G495778AR8401726'}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_all_good(env, client, monkeypatch):
|
||||
charge = get_test_charge(env[1])
|
||||
@@ -191,6 +223,7 @@ def test_webhook_global(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.save()
|
||||
order.payments.update(state=OrderPayment.PAYMENT_STATE_PENDING)
|
||||
|
||||
charge = get_test_charge(env[1])
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
@@ -231,6 +264,7 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.save()
|
||||
order.payments.update(state=OrderPayment.PAYMENT_STATE_PENDING)
|
||||
|
||||
charge = get_test_charge(env[1])
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
@@ -269,8 +303,10 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
def test_webhook_refund1(env, client, monkeypatch):
|
||||
charge = get_test_charge(env[1])
|
||||
charge['state'] = 'refunded'
|
||||
refund = get_test_refund(env[1])
|
||||
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
monkeypatch.setattr("paypalrestsdk.Refund.find", lambda *args: refund)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal.init_api", lambda *args: None)
|
||||
|
||||
client.post('/dummy/dummy/paypal/webhook/', json.dumps(
|
||||
@@ -309,21 +345,22 @@ def test_webhook_refund1(env, client, monkeypatch):
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
ra = RequiredAction.objects.get(action_type="pretix.plugins.paypal.refund")
|
||||
client.login(username='dummy@dummy.dummy', password='dummy')
|
||||
client.post('/control/event/dummy/dummy/paypal/refund/{}/'.format(ra.pk))
|
||||
|
||||
order = env[1]
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_REFUNDED
|
||||
r = order.refunds.first()
|
||||
assert r.provider == 'paypal'
|
||||
assert r.amount == order.total
|
||||
assert r.payment == order.payments.first()
|
||||
assert r.state == OrderRefund.REFUND_STATE_EXTERNAL
|
||||
assert r.source == OrderRefund.REFUND_SOURCE_EXTERNAL
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_refund2(env, client, monkeypatch):
|
||||
charge = get_test_charge(env[1])
|
||||
charge['state'] = 'refunded'
|
||||
refund = get_test_refund(env[1])
|
||||
|
||||
monkeypatch.setattr("paypalrestsdk.Sale.find", lambda *args: charge)
|
||||
monkeypatch.setattr("paypalrestsdk.Refund.find", lambda *args: refund)
|
||||
monkeypatch.setattr("pretix.plugins.paypal.payment.Paypal.init_api", lambda *args: None)
|
||||
|
||||
client.post('/dummy/dummy/paypal/webhook/', json.dumps(
|
||||
@@ -331,7 +368,7 @@ def test_webhook_refund2(env, client, monkeypatch):
|
||||
# Sample obtained in the webhook simulator
|
||||
"id": "WH-2N242548W9943490U-1JU23391CS4765624",
|
||||
"create_time": "2014-10-31T15:42:24Z",
|
||||
"resource_type": "sale",
|
||||
"resource_type": "refund",
|
||||
"event_type": "PAYMENT.SALE.REFUNDED",
|
||||
"summary": "A 0.01 USD sale payment was refunded",
|
||||
"resource": {
|
||||
@@ -356,10 +393,9 @@ def test_webhook_refund2(env, client, monkeypatch):
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
ra = RequiredAction.objects.get(action_type="pretix.plugins.paypal.refund")
|
||||
client.login(username='dummy@dummy.dummy', password='dummy')
|
||||
client.post('/control/event/dummy/dummy/paypal/refund/{}/'.format(ra.pk))
|
||||
|
||||
order = env[1]
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_REFUNDED
|
||||
r = order.refunds.first()
|
||||
assert r.provider == 'paypal'
|
||||
assert r.amount == order.total
|
||||
assert r.payment == order.payments.first()
|
||||
assert r.state == OrderRefund.REFUND_STATE_EXTERNAL
|
||||
assert r.source == OrderRefund.REFUND_SOURCE_EXTERNAL
|
||||
|
||||
@@ -32,7 +32,7 @@ def env():
|
||||
o1 = Order.objects.create(
|
||||
code='FOO', event=event, status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer'
|
||||
total=0
|
||||
)
|
||||
op1 = OrderPosition.objects.create(
|
||||
order=o1, item=shirt, variation=shirt_red,
|
||||
|
||||
@@ -35,7 +35,7 @@ def env():
|
||||
o1 = Order.objects.create(
|
||||
code='FOO', event=event, status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=0, payment_provider='banktransfer'
|
||||
total=0
|
||||
)
|
||||
op1 = OrderPosition.objects.create(
|
||||
order=o1, item=shirt, variation=shirt_red,
|
||||
|
||||
@@ -5,9 +5,9 @@ from decimal import Decimal
|
||||
import pytest
|
||||
from django.test import RequestFactory
|
||||
from django.utils.timezone import now
|
||||
from stripe import APIConnectionError, CardError, StripeError
|
||||
from stripe.error import APIConnectionError, CardError, StripeError
|
||||
|
||||
from pretix.base.models import Event, Order, Organizer
|
||||
from pretix.base.models import Event, Order, OrderRefund, Organizer
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.plugins.stripe.payment import StripeCC
|
||||
|
||||
@@ -23,7 +23,7 @@ def env():
|
||||
code='FOOBAR', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('13.37'), payment_provider='banktransfer'
|
||||
total=Decimal('13.37')
|
||||
)
|
||||
return event, o1
|
||||
|
||||
@@ -39,11 +39,16 @@ def factory():
|
||||
return RequestFactory()
|
||||
|
||||
|
||||
class MockedRefunds():
|
||||
pass
|
||||
|
||||
|
||||
class MockedCharge():
|
||||
def __init__(self):
|
||||
self.status = ''
|
||||
self.paid = False
|
||||
self.id = 'ch_123345345'
|
||||
self.refunds = MockedRefunds()
|
||||
|
||||
def refresh(self):
|
||||
pass
|
||||
@@ -72,7 +77,10 @@ def test_perform_success(env, factory, monkeypatch):
|
||||
req.session = {}
|
||||
prov.checkout_prepare(req, {})
|
||||
assert 'payment_stripe_token' in req.session
|
||||
prov.payment_perform(req, order)
|
||||
payment = order.payments.create(
|
||||
provider='stripe_cc', amount=order.total
|
||||
)
|
||||
prov.execute_payment(req, payment)
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
@@ -102,7 +110,10 @@ def test_perform_success_zero_decimal_currency(env, factory, monkeypatch):
|
||||
req.session = {}
|
||||
prov.checkout_prepare(req, {})
|
||||
assert 'payment_stripe_token' in req.session
|
||||
prov.payment_perform(req, order)
|
||||
payment = order.payments.create(
|
||||
provider='stripe_cc', amount=order.total
|
||||
)
|
||||
prov.execute_payment(req, payment)
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
@@ -125,7 +136,10 @@ def test_perform_card_error(env, factory, monkeypatch):
|
||||
prov.checkout_prepare(req, {})
|
||||
assert 'payment_stripe_token' in req.session
|
||||
with pytest.raises(PaymentException):
|
||||
prov.payment_perform(req, order)
|
||||
payment = order.payments.create(
|
||||
provider='stripe_cc', amount=order.total
|
||||
)
|
||||
prov.execute_payment(req, payment)
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
@@ -148,7 +162,10 @@ def test_perform_stripe_error(env, factory, monkeypatch):
|
||||
prov.checkout_prepare(req, {})
|
||||
assert 'payment_stripe_token' in req.session
|
||||
with pytest.raises(PaymentException):
|
||||
prov.payment_perform(req, order)
|
||||
payment = order.payments.create(
|
||||
provider='stripe_cc', amount=order.total
|
||||
)
|
||||
prov.execute_payment(req, payment)
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
@@ -175,7 +192,10 @@ def test_perform_failed(env, factory, monkeypatch):
|
||||
prov.checkout_prepare(req, {})
|
||||
assert 'payment_stripe_token' in req.session
|
||||
with pytest.raises(PaymentException):
|
||||
prov.payment_perform(req, order)
|
||||
payment = order.payments.create(
|
||||
provider='stripe_cc', amount=order.total
|
||||
)
|
||||
prov.execute_payment(req, payment)
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
@@ -185,26 +205,26 @@ def test_refund_success(env, factory, monkeypatch):
|
||||
event, order = env
|
||||
|
||||
def charge_retr(*args, **kwargs):
|
||||
def refund_create():
|
||||
def refund_create(amount):
|
||||
pass
|
||||
|
||||
c = MockedCharge()
|
||||
c.refunds = MockedCharge()
|
||||
c.refunds.create = refund_create
|
||||
return c
|
||||
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
|
||||
order.status = Order.STATUS_PAID
|
||||
order.payment_info = json.dumps({
|
||||
p = order.payments.create(provider='stripe_cc', amount=order.total, info=json.dumps({
|
||||
'id': 'ch_123345345'
|
||||
})
|
||||
}))
|
||||
order.save()
|
||||
prov = StripeCC(event)
|
||||
req = factory.post('/', data={'auto_refund': 'auto'})
|
||||
req.user = None
|
||||
prov.order_control_refund_perform(req, order)
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_REFUNDED
|
||||
refund = order.refunds.create(
|
||||
provider='stripe_cc', amount=order.total, payment=p,
|
||||
)
|
||||
prov.execute_refund(refund)
|
||||
refund.refresh_from_db()
|
||||
assert refund.state == OrderRefund.REFUND_STATE_DONE
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -212,23 +232,24 @@ def test_refund_unavailable(env, factory, monkeypatch):
|
||||
event, order = env
|
||||
|
||||
def charge_retr(*args, **kwargs):
|
||||
def refund_create():
|
||||
def refund_create(amount):
|
||||
raise APIConnectionError(message='Foo')
|
||||
|
||||
c = MockedCharge()
|
||||
c.refunds = object()
|
||||
c.refunds.create = refund_create()
|
||||
c.refunds.create = refund_create
|
||||
return c
|
||||
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
|
||||
order.status = Order.STATUS_PAID
|
||||
order.payment_info = json.dumps({
|
||||
p = order.payments.create(provider='stripe_cc', amount=order.total, info=json.dumps({
|
||||
'id': 'ch_123345345'
|
||||
})
|
||||
}))
|
||||
order.save()
|
||||
prov = StripeCC(event)
|
||||
req = factory.get('/')
|
||||
req.user = None
|
||||
prov.order_control_refund_perform(req, order)
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
refund = order.refunds.create(
|
||||
provider='stripe_cc', amount=order.total, payment=p
|
||||
)
|
||||
with pytest.raises(PaymentException):
|
||||
prov.execute_refund(refund)
|
||||
refund.refresh_from_db()
|
||||
assert refund.state != OrderRefund.REFUND_STATE_DONE
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Order, Organizer, RequiredAction, Team, User,
|
||||
Event, Order, OrderPayment, OrderRefund, Organizer, Team, User,
|
||||
)
|
||||
from pretix.plugins.stripe.models import ReferencedStripeObject
|
||||
|
||||
@@ -26,7 +26,7 @@ def env():
|
||||
code='FOOBAR', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PAID,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('13.37'), payment_provider='stripe'
|
||||
total=Decimal('13.37'),
|
||||
)
|
||||
return event, o1
|
||||
|
||||
@@ -129,7 +129,7 @@ def test_webhook_all_good(env, client, monkeypatch):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
def test_webhook_mark_paid_without_reference_and_payment(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.save()
|
||||
@@ -164,6 +164,13 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_partial_refund(env, client, monkeypatch):
|
||||
charge = get_test_charge(env[1])
|
||||
|
||||
payment = env[1].payments.create(
|
||||
provider='stripe', amount=env[1].total, info=json.dumps(charge)
|
||||
)
|
||||
ReferencedStripeObject.objects.create(order=env[1], reference="ch_18TY6GGGWE2Ias8TZHanef25",
|
||||
payment=payment)
|
||||
|
||||
charge['refunds'] = {
|
||||
"object": "list",
|
||||
"data": [
|
||||
@@ -209,13 +216,10 @@ def test_webhook_partial_refund(env, client, monkeypatch):
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
ra = RequiredAction.objects.get(action_type="pretix.plugins.stripe.refund")
|
||||
client.login(username='dummy@dummy.dummy', password='dummy')
|
||||
client.post('/control/event/dummy/dummy/stripe/refund/{}/'.format(ra.pk))
|
||||
|
||||
order = env[1]
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_REFUNDED
|
||||
ra = order.refunds.first()
|
||||
assert ra.state == OrderRefund.REFUND_STATE_EXTERNAL
|
||||
assert ra.source == 'external'
|
||||
assert ra.amount == Decimal('123.00')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -227,6 +231,48 @@ def test_webhook_global(env, client, monkeypatch):
|
||||
charge = get_test_charge(env[1])
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", lambda *args, **kwargs: charge)
|
||||
|
||||
payment = order.payments.create(
|
||||
provider='stripe', amount=order.total, info=json.dumps(charge), state=OrderPayment.PAYMENT_STATE_CREATED
|
||||
)
|
||||
ReferencedStripeObject.objects.create(order=order, reference="ch_18TY6GGGWE2Ias8TZHanef25",
|
||||
payment=payment)
|
||||
|
||||
client.post('/_stripe/webhook/', json.dumps(
|
||||
{
|
||||
"id": "evt_18otImGGWE2Ias8TUyVRDB1G",
|
||||
"object": "event",
|
||||
"api_version": "2016-03-07",
|
||||
"created": 1472729052,
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "ch_18TY6GGGWE2Ias8TZHanef25",
|
||||
"object": "charge",
|
||||
# Rest of object is ignored anway
|
||||
}
|
||||
},
|
||||
"livemode": True,
|
||||
"pending_webhooks": 1,
|
||||
"request": "req_977XOWC8zk51Z9",
|
||||
"type": "charge.succeeded"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_global_legacy_reference(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.save()
|
||||
|
||||
charge = get_test_charge(env[1])
|
||||
monkeypatch.setattr("stripe.Charge.retrieve", lambda *args, **kwargs: charge)
|
||||
|
||||
payment = order.payments.create(
|
||||
provider='stripe', amount=order.total, info=json.dumps(charge), state=OrderPayment.PAYMENT_STATE_CREATED
|
||||
)
|
||||
ReferencedStripeObject.objects.create(order=order, reference="ch_18TY6GGGWE2Ias8TZHanef25")
|
||||
|
||||
client.post('/_stripe/webhook/', json.dumps(
|
||||
@@ -251,3 +297,4 @@ def test_webhook_global(env, client, monkeypatch):
|
||||
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
assert list(order.payments.all()) == [payment]
|
||||
|
||||
@@ -39,7 +39,7 @@ def order(item):
|
||||
o = Order.objects.create(event=item.event, status=Order.STATUS_PENDING,
|
||||
expires=now() + datetime.timedelta(hours=1),
|
||||
total=13, code='DUMMY', email='dummy@dummy.test',
|
||||
datetime=now(), payment_provider='banktransfer', locale='en')
|
||||
datetime=now(), locale='en')
|
||||
OrderPosition.objects.create(order=o, item=item, price=13)
|
||||
return o
|
||||
|
||||
@@ -148,7 +148,7 @@ def test_sendmail_multi_locales(logged_in_client, sendmail_url, event, item):
|
||||
o = Order.objects.create(event=item.event, status=Order.STATUS_PAID,
|
||||
expires=now() + datetime.timedelta(hours=1),
|
||||
total=13, code='DUMMY', email='dummy@dummy.test',
|
||||
datetime=now(), payment_provider='banktransfer',
|
||||
datetime=now(),
|
||||
locale='de')
|
||||
OrderPosition.objects.create(order=o, item=item, price=13)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ def env0():
|
||||
code='FOOBAR', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING,
|
||||
datetime=now(), expires=now() + timedelta(days=10),
|
||||
total=Decimal('13.37'), payment_provider='banktransfer'
|
||||
total=Decimal('13.37'),
|
||||
)
|
||||
shirt = Item.objects.create(event=event, name='T-Shirt', default_price=12)
|
||||
shirt_red = ItemVariation.objects.create(item=shirt, default_price=14, value="Red")
|
||||
|
||||
@@ -11,7 +11,7 @@ from pretix.base.models import (
|
||||
Event, Item, ItemCategory, ItemVariation, Order, OrderPosition, Organizer,
|
||||
Question, Quota,
|
||||
)
|
||||
from pretix.base.models.orders import OrderFee
|
||||
from pretix.base.models.orders import OrderFee, OrderPayment
|
||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
from pretix.base.services.invoices import generate_invoice
|
||||
|
||||
@@ -54,7 +54,6 @@ class OrdersTest(TestCase):
|
||||
datetime=now() - datetime.timedelta(days=3),
|
||||
expires=now() + datetime.timedelta(days=11),
|
||||
total=Decimal("23"),
|
||||
payment_provider='banktransfer',
|
||||
locale='en'
|
||||
)
|
||||
self.ticket_pos = OrderPosition.objects.create(
|
||||
@@ -400,11 +399,185 @@ class OrdersTest(TestCase):
|
||||
)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
|
||||
def test_pay_wrong_payment_state(self):
|
||||
p = self.order.payments.create(
|
||||
provider='manual',
|
||||
state=OrderPayment.PAYMENT_STATE_CANCELED,
|
||||
amount=Decimal('10.00'),
|
||||
)
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%d/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret,
|
||||
p.pk),
|
||||
follow=True
|
||||
)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%d/confirm' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret,
|
||||
p.pk),
|
||||
follow=True
|
||||
)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%d/complete' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret,
|
||||
p.pk),
|
||||
follow=True
|
||||
)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
|
||||
def test_pay_wrong_order_state(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
p = self.order.payments.create(
|
||||
provider='manual',
|
||||
state=OrderPayment.PAYMENT_STATE_PENDING,
|
||||
amount=Decimal('10.00'),
|
||||
)
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%d/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret,
|
||||
p.pk),
|
||||
follow=True
|
||||
)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%d/confirm' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret,
|
||||
p.pk),
|
||||
follow=True
|
||||
)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%d/complete' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret,
|
||||
p.pk),
|
||||
follow=True
|
||||
)
|
||||
assert 'alert-danger' in response.rendered_content
|
||||
|
||||
def test_pay_change_link(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
p = self.order.payments.create(
|
||||
provider='banktransfer',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
amount=self.order.total,
|
||||
)
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
follow=True
|
||||
)
|
||||
assert '/pay/change' not in response.rendered_content
|
||||
self.order.status = Order.STATUS_PENDING
|
||||
self.order.save()
|
||||
p.state = OrderPayment.PAYMENT_STATE_PENDING
|
||||
p.save()
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
follow=True
|
||||
)
|
||||
assert '/pay/change' in response.rendered_content
|
||||
p.provider = 'testdummy'
|
||||
p.save()
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
follow=True
|
||||
)
|
||||
assert '/pay/change' not in response.rendered_content
|
||||
|
||||
def test_change_paymentmethod_partial(self):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__fee_reverse_calc', False)
|
||||
self.event.settings.set('payment_testdummy__fee_percent', '10.00')
|
||||
self.order.payments.create(
|
||||
provider='manual',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
amount=Decimal('10.00'),
|
||||
)
|
||||
|
||||
generate_invoice(self.order)
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
)
|
||||
assert 'Test dummy' in response.rendered_content
|
||||
assert '+ €1.30' in response.rendered_content
|
||||
self.client.post(
|
||||
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
{
|
||||
'payment': 'testdummy'
|
||||
}
|
||||
)
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.payments.last().provider == 'testdummy'
|
||||
fee = self.order.fees.filter(fee_type=OrderFee.FEE_TYPE_PAYMENT).last()
|
||||
assert fee.value == Decimal('1.30')
|
||||
assert self.order.total == Decimal('23.00') + fee.value
|
||||
assert self.order.invoices.count() == 3
|
||||
p = self.order.payments.last()
|
||||
assert p.provider == 'testdummy'
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CREATED
|
||||
assert p.amount == Decimal('14.30')
|
||||
|
||||
def test_change_paymentmethod_partial_with_previous_fee(self):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__fee_reverse_calc', False)
|
||||
self.event.settings.set('payment_testdummy__fee_percent', '10.00')
|
||||
f = self.order.fees.create(
|
||||
fee_type=OrderFee.FEE_TYPE_PAYMENT,
|
||||
value='1.40'
|
||||
)
|
||||
self.order.total += Decimal('1.4')
|
||||
self.order.save()
|
||||
self.order.payments.create(
|
||||
provider='manual',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
amount=Decimal('11.40'),
|
||||
fee=f
|
||||
)
|
||||
|
||||
generate_invoice(self.order)
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
)
|
||||
assert 'Test dummy' in response.rendered_content
|
||||
assert '+ €1.30' in response.rendered_content
|
||||
self.client.post(
|
||||
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
{
|
||||
'payment': 'testdummy'
|
||||
}
|
||||
)
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.payments.last().provider == 'testdummy'
|
||||
fee = self.order.fees.filter(fee_type=OrderFee.FEE_TYPE_PAYMENT).last()
|
||||
assert fee.value == Decimal('1.30')
|
||||
assert self.order.total == Decimal('24.40') + fee.value
|
||||
assert self.order.invoices.count() == 3
|
||||
p = self.order.payments.last()
|
||||
assert p.provider == 'testdummy'
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CREATED
|
||||
assert p.amount == Decimal('14.30')
|
||||
self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.order.secret, p.pk),
|
||||
{}
|
||||
)
|
||||
self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/%s/confirm' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.order.secret, p.pk),
|
||||
{}
|
||||
)
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CREATED
|
||||
|
||||
def test_change_paymentmethod_available(self):
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__fee_abs', '12.00')
|
||||
generate_invoice(self.order)
|
||||
self.order.payments.create(
|
||||
provider='banktransfer',
|
||||
state=OrderPayment.PAYMENT_STATE_PENDING,
|
||||
amount=self.order.total,
|
||||
)
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
)
|
||||
@@ -417,7 +590,12 @@ class OrdersTest(TestCase):
|
||||
}
|
||||
)
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.payment_provider == 'testdummy'
|
||||
p = self.order.payments.last()
|
||||
assert p.provider == 'testdummy'
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CREATED
|
||||
p0 = self.order.payments.first()
|
||||
assert p0.state == OrderPayment.PAYMENT_STATE_CANCELED
|
||||
assert p0.provider == 'banktransfer'
|
||||
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||
assert fee.value == Decimal('12.00')
|
||||
assert self.order.total == Decimal('23.00') + fee.value
|
||||
|
||||
@@ -23,7 +23,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
datetime=now() - datetime.timedelta(days=3),
|
||||
expires=now() + datetime.timedelta(days=11),
|
||||
total=Decimal("23"),
|
||||
payment_provider='banktransfer',
|
||||
locale='en'
|
||||
)
|
||||
self.ticket_pos = OrderPosition.objects.create(
|
||||
@@ -57,8 +56,8 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
response = self.client.get('/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.order.secret))
|
||||
assert 'X-Frame-Options' not in response
|
||||
response = self.client.get('/%s/%s/order/%s/%s/pay' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.order.secret))
|
||||
response = self.client.get('/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.order.secret))
|
||||
assert 'X-Frame-Options' not in response
|
||||
response = self.client.get('/%s/%s/order/%s/%s/cancel' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.order.secret))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import logging
|
||||
|
||||
from django.http import HttpRequest
|
||||
|
||||
from pretix.base.payment import BasePaymentProvider
|
||||
|
||||
logger = logging.getLogger('tests.testdummy.ticketoutput')
|
||||
@@ -8,11 +10,9 @@ logger = logging.getLogger('tests.testdummy.ticketoutput')
|
||||
class DummyPaymentProvider(BasePaymentProvider):
|
||||
identifier = 'testdummy'
|
||||
verbose_name = 'Test dummy'
|
||||
abort_pending_allowed = False
|
||||
|
||||
def order_pending_render(self, request, order) -> str:
|
||||
pass
|
||||
|
||||
def payment_is_valid_session(self, request) -> bool:
|
||||
def payment_is_valid_session(self, request: HttpRequest) -> bool:
|
||||
pass
|
||||
|
||||
def checkout_confirm_render(self, request) -> str:
|
||||
|
||||
Reference in New Issue
Block a user