Fix #571 -- Partial payments and refunds

This commit is contained in:
Raphael Michel
2018-06-26 12:09:36 +02:00
parent 8e7af49206
commit 18a378976b
115 changed files with 6026 additions and 1598 deletions

View File

@@ -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(

View File

@@ -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),
{

View File

@@ -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)

View File

@@ -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

View File

@@ -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),

View File

@@ -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'),

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)

View File

@@ -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,

View File

@@ -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',

View File

@@ -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

View File

@@ -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',

View File

@@ -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,

View File

@@ -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

View File

@@ -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/",

View File

@@ -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,

View File

@@ -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'
})

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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])

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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]

View File

@@ -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)

View File

@@ -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")

View File

@@ -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

View File

@@ -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))

View File

@@ -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: