combo/tests/test_lingo_payment.py

1305 lines
54 KiB
Python

from contextlib import contextmanager
import eopayment
import pytest
from datetime import datetime, timedelta
import urllib
from decimal import Decimal
import json
import mock
from django.apps import apps
from django.contrib.auth.models import User
from django.urls import reverse
from django.core.wsgi import get_wsgi_application
from django.conf import settings
from django.test import override_settings
from django.utils import timezone
from django.utils.http import urlencode
from django.utils.six.moves.urllib import parse as urlparse
from django.contrib.messages.storage.session import SessionStorage
from webtest import TestApp
from combo.data.models import Page
from combo.apps.lingo.models import (
Regie, BasketItem, Transaction, TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell,
PaymentBackend)
from combo.utils import aes_hex_decrypt, sign_url
from combo.apps.lingo.views import signing_loads, signing_dumps
from .test_manager import login
pytestmark = pytest.mark.django_db
def get_url(with_payment_backend, view_name, regie):
if with_payment_backend:
return reverse(
view_name + '-payment-backend',
kwargs={'payment_backend_pk': regie.payment_backend.id})
return reverse(view_name, kwargs={'regie_pk': regie.id})
@contextmanager
def check_log(caplog, message):
idx = len(caplog.records)
yield
assert any(message in record.message for record in caplog.records[idx:]), \
'%r not found in log records' % message
@pytest.fixture
def regie():
try:
payment_backend = PaymentBackend.objects.get(slug='test1')
except PaymentBackend.DoesNotExist:
payment_backend = PaymentBackend.objects.create(
label='test1', slug='test1', service='dummy', service_options={'siret': '1234'})
try:
regie = Regie.objects.get(slug='test')
except Regie.DoesNotExist:
regie = Regie()
regie.label = 'Test'
regie.slug = 'test'
regie.description = 'test'
regie.payment_min_amount = Decimal(4.5)
regie.payment_backend = payment_backend
regie.save()
return regie
@pytest.fixture
def remote_regie():
try:
payment_backend = PaymentBackend.objects.get(slug='test1')
except PaymentBackend.DoesNotExist:
payment_backend = PaymentBackend.objects.create(
label='test1', slug='test1', service='dummy', service_options={'siret': '1234'})
try:
regie = Regie.objects.get(slug='remote')
except Regie.DoesNotExist:
regie = Regie()
regie.label = 'Remote'
regie.slug = 'remote'
regie.description = 'remote'
regie.payment_min_amount = Decimal(2.0)
regie.payment_backend = payment_backend
regie.webservice_url = 'http://example.org/regie' # is_remote
regie.save()
return regie
@pytest.fixture
def basket_page():
page = Page(title='xxx', slug='test_basket_cell', template_name='standard')
page.save()
cell = LingoBasketCell(page=page, placeholder='content', order=0)
cell.save()
return page
@pytest.fixture
def user():
try:
user = User.objects.get(username='admin')
except User.DoesNotExist:
user = User.objects.create_user('admin', password='admin',
email='foo@example.com')
return user
@pytest.fixture(params=['orig', 'sign_key'])
def key(request, settings):
if request.param == 'orig':
key = 'abcde'
settings.KNOWN_SERVICES = {
'wcs': {
'wcs1': {
'url': 'http://example.org/',
'orig': 'http://combo.example.net/',
'verif_orig': 'wcs',
'secret': key,
},
}
}
return key
else:
return settings.LINGO_API_SIGN_KEY
def assert_payment_status(url, transaction_id=None):
if hasattr(url, 'path'):
url = url.path
if transaction_id:
url, part = url.split('?')
query = urlparse.parse_qs(part)
assert 'transaction-id' in query
assert ':' not in query['transaction-id']
assert signing_loads(query['transaction-id'][0]) == transaction_id
assert url.startswith('/lingo/payment-status')
def test_default_regie():
payment_backend = PaymentBackend.objects.create(label='foo', slug='foo')
Regie.objects.all().delete()
regie1 = Regie(label='foo', slug='foo', payment_backend=payment_backend)
regie1.save()
assert bool(regie1.is_default) is True
regie2 = Regie(label='bar', slug='bar', payment_backend=payment_backend)
regie2.save()
assert bool(regie2.is_default) is False
regie2.is_default = True
regie2.save()
regie2 = Regie.objects.get(id=regie2.id)
assert bool(regie2.is_default) is True
regie1 = Regie.objects.get(id=regie1.id)
assert bool(regie1.is_default) is False
def test_regie_api(app):
resp = app.get(reverse('api-regies'))
assert len(json.loads(resp.text).get('data')) == 0
test_default_regie()
resp = app.get(reverse('api-regies'))
assert len(json.loads(resp.text).get('data')) == 2
assert json.loads(resp.text).get('data')[0]['id'] == Regie.objects.get(is_default=True).slug
def test_payment_min_amount(app, basket_page, regie, user):
items = {'item1': {'amount': '1.5', 'source_url': '/item/1'},
'item2': {'amount': '2.4', 'source_url': '/item/2'}
}
b_items = []
for subject, details in items.items():
b_item = BasketItem.objects.create(user=user, regie=regie,
subject=subject, **details)
b_items.append(b_item.pk)
resp = login(app).get('/test_basket_cell/')
resp = resp.form.submit()
assert resp.status_code == 302
def test_transaction_manual_validation(app, basket_page, user, monkeypatch):
pb = PaymentBackend.objects.create(
label='test1', slug='test1', service='payzen',
service_options={'vads_site_id': '12345678', 'secret_test': 'plkoBdfcx987dhft6'}
)
regie = Regie.objects.create(
label='Test', slug='test', description='test', payment_backend=pb,
transaction_options={'manual_validation': True})
BasketItem.objects.create(
user=user, regie=regie, subject='item1', amount='1.5', source_url='/item/1')
class MockPayment(object):
request = mock.Mock(return_value=(9876, 3, {}))
def get_eopayment_object(*args, **kwargs):
return MockPayment
import combo.apps.lingo.views
monkeypatch.setattr(combo.apps.lingo.views, 'get_eopayment_object', get_eopayment_object)
resp = login(app).get('/test_basket_cell/')
resp = resp.form.submit()
assert MockPayment.request.call_args[1]['manual_validation'] is True
@pytest.mark.parametrize('with_payment_backend', [False, True])
def test_successfull_items_payment(app, basket_page, regie, user, with_payment_backend):
items = {'item1': {'amount': '10.5', 'source_url': 'http://example.org/item/1'},
'item2': {'amount': '42', 'source_url': 'http://example.org/item/2'},
'item3': {'amount': '100', 'source_url': 'http://example.org/item/3'},
'item4': {'amount': '354', 'source_url': 'http://example.org/item/4'}
}
b_items = []
for subject, details in items.items():
b_item = BasketItem.objects.create(user=user, regie=regie,
subject=subject, **details)
b_items.append(b_item.pk)
resp = login(app).get('/test_basket_cell/')
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
assert 'dummy-payment' in location
parsed = urlparse.urlparse(location)
# get return_url and transaction id from location
qs = urlparse.parse_qs(parsed.query)
args = {'transaction_id': qs['transaction_id'][0], 'signed': True,
'ok': True, 'reason': 'Paid'}
# make sure return url is the user return URL
assert urlparse.urlparse(qs['return_url'][0]).path.startswith(
'/lingo/return-payment-backend/%s/' % regie.payment_backend.id)
# simulate successful call to callback URL
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
resp = app.get(get_url(with_payment_backend, 'lingo-callback', regie), params=args)
assert resp.status_code == 200
# simulate successful return URL
resp = app.get(qs['return_url'][0], params=args)
# redirect to payment status
assert resp.status_code == 302
assert urlparse.urlparse(resp.url).path.startswith('/lingo/payment-status')
resp = resp.follow()
assert 'Your payment has been succesfully registered.' in resp.text
assert urlparse.urlparse(resp.html.find('a', {'id': 'next-url'})['href']).path == \
'/test_basket_cell/'
def test_add_amount_to_basket(app, key, regie, user):
payment_backend = PaymentBackend.objects.create(
label='test2', slug='test2', service='dummy', service_options={'siret': '1234'})
other_regie = Regie(label='test2', slug='test2', payment_backend=payment_backend)
other_regie.save()
user_email = 'foo@example.com'
User.objects.get_or_create(email=user_email)
data = {'display_name': 'test amount',
'url': 'http://example.com'}
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
resp = app.post_json(url, params=data, status=400)
assert 'missing amount parameter' in resp.text
amount = 42
data['amount'] = amount
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), 'unknown@example.com')
url = sign_url(url, key)
resp = app.post_json(url, params=data, status=400)
assert 'unknown user' in resp.text
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=amount).exists()
assert BasketItem.objects.filter(amount=amount)[0].regie_id == regie.id
resp = app.post_json('%s&amount=10' % url, params=data, status=403) # bad signature
data['extra'] = {'amount': '22.22'}
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=Decimal('64.22')).exists()
data['amount'] = [amount]
data['extra'] = {'amount': ['22.22', '12']}
url = '%s?email=%s&orig=wcs&amount=5' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=Decimal('81.22')).exists()
# accept french notation if settings.LANGUAGE_CODE is 'fr-*'
url = '%s?amount=10,00&email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
resp = app.post_json(url, params=data, status=400)
assert 'invalid value for "amount" in query string' in resp.text
data['amount'] = '1,10'
url = '%s?amount=10.00&email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
resp = app.post_json(url, params=data, status=400)
assert 'invalid value for "amount" in payload' in resp.text
data['amount'] = '1.10'
data['extra'] = {'amount': '0,01'}
resp = app.post_json(url, params=data, status=400)
assert 'invalid value for "amount" in extra payload' in resp.text
data['amount'] = '1,10'
data['extra'] = {'amount': '0,01'}
url = '%s?amount=10,00&email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
with override_settings(LANGUAGE_CODE='fr-be'):
resp = app.post_json(url, params=data, status=200)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=Decimal('11.11')).exists()
other_regie.is_default = True
other_regie.save()
data['amount'] = []
data['extra'] = {'amount': '22.23'}
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
resp = app.post_json(url, params=data)
item = BasketItem.objects.get(amount=Decimal('22.23'))
assert resp.status_code == 200
response = json.loads(resp.text)
assert response['result'] == 'success'
payment_url = urlparse.urlparse(response['payment_url'])
assert payment_url.path.startswith('/lingo/item/')
assert payment_url.path.endswith('/pay')
assert BasketItem.objects.filter(amount=Decimal('22.23')).exists()
assert BasketItem.objects.filter(amount=Decimal('22.23'))[0].regie_id == other_regie.id
url = '%s?email=%s&regie_id=%s' % (
reverse('api-add-basket-item'), user_email, regie.id)
data['extra'] = {'amount': '22.24', 'foo': 'bar'}
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=Decimal('22.24')).exists()
assert BasketItem.objects.filter(amount=Decimal('22.24'))[0].regie_id == regie.id
assert BasketItem.objects.filter(amount=Decimal('22.24'))[0].request_data == data['extra']
url = '%s?email=%s&regie_id=%s' % (
reverse('api-add-basket-item'), user_email, regie.slug)
data['extra'] = {'amount': '13.67'}
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=Decimal('13.67')).exists()
assert BasketItem.objects.filter(amount=Decimal('13.67'))[0].regie_id == regie.id
url = '%s?email=%s&orig=wcs&regie_id=%s' % (reverse('api-add-basket-item'), user_email, 'scarecrow')
url = sign_url(url, key)
resp = app.post_json(url, params=data, status=400)
assert resp.text == 'Unknown regie'
def test_basket_item_with_capture_date(app, user, regie, basket_page, monkeypatch):
User.objects.get_or_create(email=user.email)
url = '%s?email=%s' % (reverse('api-add-basket-item'), user.email)
capture_date = timezone.now().date()
data = {
'amount': 10, 'capture_date': capture_date.isoformat(),
'display_name': 'test item'
}
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert BasketItem.objects.all()[0].capture_date == capture_date
resp = login(app).get('/test_basket_cell/')
import eopayment
eopayment_mock = mock.Mock(
return_value=('orderid', eopayment.URL, 'http://dummy-payment.demo.entrouvert.com/'))
monkeypatch.setattr(eopayment.Payment, 'request', eopayment_mock)
resp = resp.form.submit()
assert resp.status_code == 302
location = urlparse.urlparse(resp.location)
assert location.path == '/'
assert location.hostname == 'dummy-payment.demo.entrouvert.com'
eopayment_mock.assert_called_once_with(
Decimal(10), email=user.email, first_name=user.first_name, last_name=user.last_name,
capture_date=capture_date)
@pytest.mark.parametrize("invalid_capture_date", [8, '', 'not-a-date'])
def test_add_basket_capture_date_format(app, user, regie, invalid_capture_date):
url = '%s?email=%s' % (reverse('api-add-basket-item'), user.email)
data = {'amount': 10, 'display_name': 'test item'}
data['capture_date'] = invalid_capture_date
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
resp = app.post_json(url, params=data, status=400)
assert 'Bad format for capture date, it should be yyyy-mm-dd.' in resp.text
def test_add_basket_item_with_remote_regie(app, user, remote_regie):
data = {'amount': 10, 'display_name': 'test item'}
url = '%s?email=%s' % (reverse('api-add-basket-item'), user.email)
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
resp = app.post_json(url, params=data, status=400)
assert 'Can not add a basket item to a remote regie.' in resp.text
def test_cant_pay_if_different_capture_date(app, basket_page, regie, user):
capture1 = (timezone.now() + timedelta(days=1)).date()
capture2 = (timezone.now() + timedelta(days=2)).date()
items = {
'item1': {
'amount': '10.5', 'capture_date': capture1.isoformat(),
'source_url': 'http://example.org/item/1'
},
'item2': {
'amount': '42', 'capture_date': capture2.isoformat(),
'source_url': 'http://example.org/item/2'},
}
b_items = []
for subject, details in items.items():
b_item = BasketItem.objects.create(
user=user, regie=regie, subject=subject, **details)
b_items.append(b_item.pk)
resp = login(app).get('/test_basket_cell/')
resp = resp.form.submit()
assert resp.status_code == 302
assert urlparse.urlparse(resp.location).path == '/test_basket_cell/'
resp = resp.follow()
assert "Invalid grouping for basket items: different capture dates." in resp.text
def test_pay_single_basket_item(app, key, regie, user, john_doe):
page = Page(title='xxx', slug='index', template_name='standard')
page.save()
cell = LingoBasketCell(page=page, placeholder='content', order=0)
cell.save()
amount = 12
data = {'amount': amount,
'display_name': 'test amount',
'url': 'http://example.com'}
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user.email)
url = sign_url(url, key)
resp = app.post_json(url, params=data)
# check that an unpaid item exists in basket
assert BasketItem.objects.filter(regie=regie, amount=amount, payment_date__isnull=True).exists()
payment_url = resp.json['payment_url']
resp = app.get(payment_url, status=403)
assert 'Wrong item: payment not allowed.' in resp.text
login(app, username='john.doe', password='john.doe')
resp = app.get(payment_url, status=403)
assert 'Wrong item: payment not allowed.' in resp.text
# forbid payment to regie with extra_fees_ws_url
regie.extra_fees_ws_url = 'http://example.com/extra-fees'
regie.save()
app.reset()
login(app)
resp = app.get(payment_url, status=403)
assert 'No item payment allowed as extra fees set.' in resp.text
regie.extra_fees_ws_url = ''
regie.save()
resp = app.get(payment_url, params={'next_url': 'http://example.net/form/id/'})
# make sure the redirection is done to the payment backend
assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
assert qs['amount'] == ['12.00']
# simulate successful payment response from dummy backend
data = {'transaction_id': qs['transaction_id'][0], 'ok': True,
'amount': qs['amount'][0], 'signed': True}
# simulate payment service redirecting the user to /lingo/return/... (eopayment
# dummy module put that URL in return_url query string parameter).
resp = app.get(qs['return_url'][0], params=data)
# check that item is paid
item = BasketItem.objects.filter(regie=regie, amount=amount, payment_date__isnull=False).first()
# check that user is redirected to the item payment status view
assert_payment_status(resp.location, transaction_id=item.transaction_set.last().pk)
def test_pay_multiple_regies(app, key, regie, user):
test_add_amount_to_basket(app, key, regie, user)
page = Page(title='xxx', slug='test_basket_cell', template_name='standard')
page.save()
cell = LingoBasketCell(page=page, placeholder='content', order=0)
cell.save()
resp = login(app).get(page.get_online_url())
resp = resp.forms[0].submit()
assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
assert qs['amount'] == ['234.46']
resp = login(app).get(page.get_online_url())
resp = resp.forms[1].submit()
qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
assert qs['amount'] == ['22.23']
def test_pay_as_anonymous_user(app, key, regie, user):
test_add_amount_to_basket(app, key, regie, user)
page = Page(title='xxx', slug='test_basket_cell', template_name='standard')
page.save()
cell = LingoBasketCell(page=page, placeholder='content', order=0)
cell.save()
resp = login(app).get(page.get_online_url())
app.cookiejar.clear(domain='testserver.local', path='/', name='sessionid')
resp = resp.forms[0].submit().follow()
assert 'Payment requires to be logged in.' in resp.text
def test_cancel_basket_item(app, key, regie, user):
user_email = 'foo@example.com'
User.objects.get_or_create(email=user_email)
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
data = {'amount': 42, 'display_name': 'test amount', 'url':
'http://example.com/', 'notify': 'true'}
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=42, cancellation_date__isnull=True).exists()
basket_item_id = json.loads(resp.text)['id']
data = {'amount': 21, 'display_name': 'test amount', 'url': 'http://example.net/'}
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=42, cancellation_date__isnull=True).exists()
assert BasketItem.objects.filter(amount=21, cancellation_date__isnull=True).exists()
basket_item_id_2 = json.loads(resp.text)['id']
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), user_email)
url = sign_url(url, key)
data = {'notify': 'true'}
resp = app.post_json(url, params=data, status=400)
assert 'missing basket_item_id parameter' in resp.text
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), user_email)
url = sign_url(url, key)
data = {'basket_item_id': 'eggs', 'notify': 'true'}
resp = app.post_json(url, params=data, status=400)
assert 'invalid basket_item_id' in resp.text
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), user_email)
url = sign_url(url, key)
data = {'basket_item_id': 0, 'notify': 'true'}
resp = app.post_json(url, params=data, status=400)
assert 'unknown basket item' in resp.text
url = '%s?orig=wcs' % (reverse('api-remove-basket-item'))
url = sign_url(url, key)
data = {'basket_item_id': basket_item_id, 'notify': 'true'}
resp = app.post_json(url, params=data, status=400)
assert 'no user specified' in resp.text
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), 'unknown@example.com')
url = sign_url(url, key)
data = {'basket_item_id': basket_item_id, 'notify': 'true'}
resp = app.post_json(url, params=data, status=400)
assert 'unknown user' in resp.text
other_user_email = 'bar@example.net'
User.objects.get_or_create(email=other_user_email)
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), other_user_email)
url = sign_url(url, key)
data = {'basket_item_id': basket_item_id, 'notify': 'true'}
resp = app.post_json(url, params=data, status=400)
assert 'user does not own the basket item' in resp.text
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), user_email)
url = sign_url(url, key)
data = {'basket_item_id': basket_item_id, 'notify': 'true'}
resp = app.post_json(url, params=data)
assert request.call_args[0] == ('POST', u'http://example.com/jump/trigger/cancelled')
assert not BasketItem.objects.filter(amount=42, cancellation_date__isnull=True).exists()
assert BasketItem.objects.filter(amount=21, cancellation_date__isnull=True).exists()
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), user_email)
url = sign_url(url, key)
data = {'basket_item_id': basket_item_id_2}
resp = app.post_json(url, params=data)
assert request.call_count == 0
assert not BasketItem.objects.filter(amount=42, cancellation_date__isnull=True).exists()
assert not BasketItem.objects.filter(amount=21, cancellation_date__isnull=True).exists()
url = '%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), user_email)
url = sign_url(url, key)
data = {'basket_item_id': basket_item_id}
resp = app.post_json(url, params=data, status=400)
assert 'basket item already cancelled' in resp.text
def test_cancel_basket_item_from_cell(app, key, regie, user):
page = Page(title='xxx', slug='test_basket_cell', template_name='standard')
page.save()
cell = LingoBasketCell(page=page, placeholder='content', order=0)
cell.save()
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user.email)
url = sign_url(url, key)
data = {'amount': 42, 'display_name': 'test amount', 'url': 'http://example.org/testitem/'}
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=42, cancellation_date__isnull=True).exists()
basket_item_id = json.loads(resp.text)['id']
# check while not logged in
resp = app.get(reverse('lingo-cancel-item', kwargs={'pk': basket_item_id}), status=404)
assert BasketItem.objects.filter(id=basket_item_id).exists()
# check a successful case
app = login(app)
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
resp = app.get(reverse('lingo-cancel-item', kwargs={'pk': basket_item_id}))
resp = resp.form.submit()
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/cancelled')
assert BasketItem.objects.filter(id=basket_item_id, cancellation_date__isnull=False).exists()
# check removal of an item that is not cancellable
url = '%s?email=%s&cancellable=no&orig=wcs' % (reverse('api-add-basket-item'), user.email)
url = sign_url(url, key)
data = {'amount': 21, 'display_name': 'test amount',
'url': 'http://example.org/testitem/'}
resp = app.post_json(url, params=data)
basket_item2_id = json.loads(resp.text)['id']
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=21, cancellation_date__isnull=True).exists()
resp = app.get(reverse('lingo-cancel-item', kwargs={'pk': basket_item2_id}))
resp = resp.form.submit()
resp = resp.follow()
assert 'This item cannot be removed.' in resp.text
# check removal of the item of another user
user_email = 'bar@example.com'
User.objects.get_or_create(email=user_email)
url = '%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email)
url = sign_url(url, key)
data = {'amount': 42, 'display_name': 'test amount', 'url': 'http://example.org/testitem/'}
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
basket_item_id = json.loads(resp.text)['id']
app.get(reverse('lingo-cancel-item', kwargs={'pk': basket_item_id}), status=404)
app.post(reverse('lingo-cancel-item', kwargs={'pk': basket_item_id}), status=403)
@pytest.mark.parametrize('with_payment_backend', [False, True])
def test_payment_callback(app, basket_page, regie, user, with_payment_backend):
page = Page(title='xxx', slug='index', template_name='standard')
page.save()
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '10.50'
# call callback with GET
callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
get_resp = app.get(callback_url, params=data)
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert get_resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == 3
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='11.5',
source_url='http://example.org/testitem/')
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
return_url = qs['return_url'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '11.50'
# call callback with POST
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
post_resp = app.post(callback_url, params=data)
assert post_resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == 3
# call return view
get_resp = app.get(return_url, params=data)
assert get_resp.status_code == 302
resp = app.get(get_resp['Location'])
assert 'Your payment has been succesfully registered.' in resp.text
@pytest.mark.parametrize('with_payment_backend', [False, True])
def test_payment_callback_no_regie(app, basket_page, regie, user, with_payment_backend):
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '10.50'
# call callback with GET
callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
get_resp = app.get(callback_url, params=data)
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert get_resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == 3
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='11.5',
source_url='http://example.org/testitem/')
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '11.50'
@pytest.mark.parametrize('with_payment_backend', [False, True])
def test_nonexisting_transaction(app, regie, user, with_payment_backend):
app = login(app)
data = {'transaction_id': 'unknown', 'signed': True,
'amount': '23', 'ok': True}
# call callback with GET
callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
app.get(callback_url, params=data, status=404)
@pytest.mark.parametrize('with_payment_backend', [False, True])
def test_payment_callback_waiting(app, basket_page, regie, user, with_payment_backend):
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'waiting': True}
assert data['amount'] == '10.50'
# callback with WAITING state
callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
resp = app.get(callback_url, params=data)
assert resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == eopayment.WAITING
assert BasketItem.objects.get(id=item.id).waiting_date
assert not BasketItem.objects.get(id=item.id).payment_date
assert BasketItem.get_items_to_be_paid(user).count() == 0
# callback with PAID state
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
resp = app.get(callback_url, params=data)
assert resp.status_code == 200
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert Transaction.objects.get(order_id=transaction_id).status == eopayment.PAID
assert BasketItem.objects.get(id=item.id).payment_date
assert BasketItem.get_items_to_be_paid(user).count() == 0
@pytest.mark.parametrize('with_payment_backend', [False, True])
def test_payment_no_callback_just_return(
caplog, app, basket_page, regie, user, with_payment_backend):
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '10.50'
return_url = qs['return_url'][0]
# call return with unsigned POST
with check_log(caplog, 'received unsigned payment'):
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
get_resp = app.post(return_url, params=data)
assert request.call_count == 0
assert get_resp.status_code == 302
assert urlparse.urlparse(get_resp['location']).path == '/lingo/payment-status'
assert Transaction.objects.get(order_id=transaction_id).status == 0 # not paid
# call return with missing data
with check_log(caplog, 'failed to process payment response: missing transaction_id'):
baddata = data.copy()
del baddata['transaction_id']
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
get_resp = app.post(return_url, params=baddata)
assert get_resp.status_code == 302
resp = app.get(get_resp['Location'])
assert 'Your payment has been succesfully registered.' not in resp.text
assert 'the payment service failed to provide a correct answer.' in resp.text
assert Transaction.objects.get(order_id=transaction_id).status == 0 # not paid
# call return with signed POST
data['signed'] = True
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
get_resp = app.post(return_url, params=data)
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
# redirect to payment status
assert get_resp.status_code == 302
assert urlparse.urlparse(get_resp.url).path.startswith('/lingo/payment-status')
resp = get_resp.follow()
assert 'Your payment has been succesfully registered.' in resp.text
assert urlparse.urlparse(resp.html.find('a', {'id': 'next-url'})['href']).path == \
'/test_basket_cell/'
assert Transaction.objects.get(order_id=transaction_id).status == eopayment.PAID
def test_transaction_expiration():
t1 = Transaction(status=0)
t1.save()
t1.start_date = timezone.now() - timedelta(hours=2)
t1.save()
t2 = Transaction(status=0)
t2.save()
appconfig = apps.get_app_config('lingo')
appconfig.update_transactions()
assert Transaction.objects.get(id=t1.id).status == EXPIRED
assert Transaction.objects.get(id=t2.id).status == 0
def test_transaction_validate(app, key, regie, user):
t1 = Transaction(regie=regie, bank_data={'bank': 'data'}, amount=12,
status=eopayment.PAID)
t1.save()
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=0'
resp = app.post_json(url, params={}, status=403)
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=0&orig=wcs'
url = sign_url(url, key)
resp = app.post_json(url, params={}, status=404)
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=%s&orig=wcs' % t1.id
url = sign_url(url, key)
resp = app.post_json(url, params={})
assert json.loads(resp.text)['err'] == 0
operations = TransactionOperation.objects.filter(transaction=t1)
assert len(operations) == 1
assert operations[0].amount == 10
with mock.patch.object(eopayment.dummy.Payment, 'validate', autospec=True) as mock_validate:
mock_validate.side_effect = eopayment.ResponseError
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=%s&orig=wcs' % t1.id
url = sign_url(url, key)
resp = app.post_json(url, params={})
assert json.loads(resp.text)['err'] == 1
assert TransactionOperation.objects.filter(transaction=t1).count() == 1
def test_transaction_cancel(app, key, regie, user):
t1 = Transaction(regie=regie, bank_data={'bank': 'data'}, amount=12,
status=eopayment.PAID)
t1.save()
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=0&orig=wcs'
resp = app.post_json(url, params={}, status=403)
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=0&orig=wcs'
url = sign_url(url, key)
resp = app.post(url, params={}, status=404)
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=%s&orig=wcs' % t1.id
url = sign_url(url, key)
resp = app.post_json(url, params={})
assert json.loads(resp.text)['err'] == 0
operations = TransactionOperation.objects.filter(transaction=t1)
assert len(operations) == 1
assert operations[0].amount == 10
with mock.patch.object(eopayment.dummy.Payment, 'cancel', autospec=True) as mock_cancel:
mock_cancel.side_effect = eopayment.ResponseError
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=%s&orig=wcs' % t1.id
url = sign_url(url, key)
resp = app.post_json(url, params={})
assert json.loads(resp.text)['err'] == 1
assert TransactionOperation.objects.filter(transaction=t1).count() == 1
def test_extra_fees(app, basket_page, key, regie, user):
regie.extra_fees_ws_url = 'http://www.example.net/extra-fees'
regie.save()
user_email = 'foo@example.com'
User.objects.get_or_create(email=user_email)
amount = 42
data = {'amount': amount, 'display_name': 'test amount'}
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
mock_json = mock.Mock()
mock_json.status_code = 200
mock_json.json.return_value = {'err': 0, 'data': [{'subject': 'Extra Fees', 'amount': '5'}]}
request.return_value = mock_json
url = sign_url('%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email), key)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert BasketItem.objects.filter(amount=amount).exists()
assert BasketItem.objects.filter(amount=amount)[0].regie_id == regie.id
assert BasketItem.objects.filter(amount=5, extra_fee=True).exists()
assert BasketItem.objects.filter(amount=5, extra_fee=True)[0].regie_id == regie.id
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
mock_json = mock.Mock()
mock_json.status_code = 200
mock_json.json.return_value = {'err': 0, 'data': [{'subject': 'Extra Fees', 'amount': '7'}]}
request.return_value = mock_json
data['amount'] = 43
url = sign_url('%s?email=%s&orig=wcs' % (reverse('api-add-basket-item'), user_email), key)
resp = app.post_json(url, params=data)
assert request.call_args[0] == ('POST', 'http://www.example.net/extra-fees')
assert len(json.loads(request.call_args[1]['data'])['data']) == 2
assert resp.status_code == 200
assert json.loads(resp.text)['result'] == 'success'
assert not BasketItem.objects.filter(amount=5, extra_fee=True).exists()
assert BasketItem.objects.filter(amount=7, extra_fee=True).exists()
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
mock_json = mock.Mock()
mock_json.status_code = 200
mock_json.json.return_value = {'err': 0, 'data': [{'subject': 'Extra Fees', 'amount': '4'}]}
request.return_value = mock_json
url = sign_url('%s?email=%s&orig=wcs' % (reverse('api-remove-basket-item'), user_email), key)
data = {'basket_item_id': BasketItem.objects.get(amount=43).id}
resp = app.post_json(url, params=data)
assert resp.status_code == 200
assert not BasketItem.objects.filter(amount=7, extra_fee=True).exists()
assert BasketItem.objects.filter(amount=4, extra_fee=True).exists()
# test payment
app = login(app)
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
mock_json = mock.Mock()
mock_json.status_code = 200
mock_json.json.return_value = {'err': 0, 'data': [{'subject': 'Extra Fees', 'amount': '2'}]}
request.return_value = mock_json
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '44.00'
# test again, without specifying a regie
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
mock_json = mock.Mock()
mock_json.status_code = 200
mock_json.json.return_value = {'err': 0, 'data': [{'subject': 'Extra Fees', 'amount': '3'}]}
request.return_value = mock_json
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '45.00'
# call callback with GET
callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
resp = app.get(callback_url, params=data)
assert resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == 3
@pytest.mark.parametrize('with_payment_backend', [False, True])
def test_payment_callback_error(app, basket_page, regie, user, with_payment_backend):
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit()
assert resp.status_code == 302
location = resp.location
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id, 'signed': True,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '10.50'
# call callback with GET
callback_url = get_url(with_payment_backend, 'lingo-callback', regie)
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
mock_response = mock.Mock()
def kaboom():
raise Exception('kaboom')
mock_response.status_code = 500
mock_response.raise_for_status = kaboom
request.return_value = mock_response
get_resp = app.get(callback_url, params=data)
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert get_resp.status_code == 200
assert Transaction.objects.get(order_id=transaction_id).status == 3
assert BasketItem.objects.get(id=item.id).payment_date
assert not BasketItem.objects.get(id=item.id).notification_date
# too soon
appconfig = apps.get_app_config('lingo')
appconfig.notify_payments()
assert BasketItem.objects.get(id=item.id).payment_date
assert not BasketItem.objects.get(id=item.id).notification_date
# fake delay
basket_item = BasketItem.objects.get(id=item.id)
basket_item.payment_date = timezone.now() - timedelta(hours=1)
basket_item.save()
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
mock_response = mock.Mock()
mock_response.status_code = 200
request.return_value = mock_response
appconfig.notify_payments()
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert BasketItem.objects.get(id=item.id).payment_date
assert BasketItem.objects.get(id=item.id).notification_date
def test_payment_callback_not_found(app, user, regie):
data = {'transaction_id': 42, 'signed': True,
'amount': 42, 'ok': True}
callback_url = reverse('lingo-callback', kwargs={'regie_pk': 0})
app.get(callback_url, params=data, status=404)
callback_url = reverse('lingo-callback-payment-backend', kwargs={'payment_backend_pk': 0})
app.get(callback_url, params=data, status=404)
@pytest.mark.parametrize("authenticated", [True, False])
def test_payment_no_basket(app, user, regie, authenticated):
url = reverse('api-add-basket-item')
source_url = 'http://example.org/item/1'
data = {'amount': 10, 'display_name': 'test item', 'url': source_url}
if authenticated:
data['email'] = user.email
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
resp = app.post_json(url, params=data)
assert resp.status_code == 200
payment_url = resp.json['payment_url']
item = BasketItem.objects.first()
assert item.user is None
assert item.amount == Decimal('10.00')
path = urlparse.urlparse(payment_url).path
start = '/lingo/item/'
end = '/pay'
assert path.startswith(start)
assert path.endswith(end)
signature = path.replace(start, '').replace(end, '')
assert signing_loads(signature) == item.id
if authenticated:
app = login(app)
# payment error due to too small amount
item.amount = Decimal('1.00')
item.save()
resp = app.get(payment_url)
assert_payment_status(resp.location)
resp = resp.follow()
assert 'Minimal payment amount is 4.50' in resp.text
# we can go back to form
assert source_url in resp.text
# amount ok, redirection to payment backend
item.amount = Decimal('10.00')
item.save()
resp = app.get(payment_url)
assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
assert qs['amount'] == ['10.00']
if authenticated:
assert qs['email'] == ['foo@example.com']
else:
assert 'email' not in qs
# mail can be specified here for anonymous user
resp = app.get(
payment_url,
params={
'email': 'foo@localhost',
}
)
assert resp.location.startswith('http://dummy-payment.demo.entrouvert.com/')
qs = urlparse.parse_qs(urlparse.urlparse(resp.location).query)
assert qs['amount'] == ['10.00']
if authenticated:
assert qs['email'] == ['foo@example.com']
else:
assert qs['email'] == ['foo@localhost']
# simulate bad responseform payment backend, no transaction id
data = {'amount': qs['amount'][0], 'signed': True}
return_url = qs['return_url'][0]
resp = app.get(return_url, params=data)
assert_payment_status(resp.location)
resp = resp.follow()
assert 'We are sorry but the payment service failed to provide a correct answer.' in resp.text
assert 'http://example.org/item/1' in resp.text
# check that item is not paid
item = BasketItem.objects.get(pk=item.pk)
assert not item.payment_date
# simulate successful payment response from dummy backend
data = {
'transaction_id': qs['transaction_id'][0], 'ok': True,
'amount': qs['amount'][0], 'signed': True
}
return_url = qs['return_url'][0]
resp = app.get(return_url, params=data)
assert_payment_status(resp.location, transaction_id=item.transaction_set.last().pk)
# check that item is paid
item = BasketItem.objects.get(pk=item.pk)
assert item.payment_date
# accept redirection to item payment status view
resp = resp.follow()
# which should it self redirect to item.source_url as it is paid
assert 'Please wait while your request is being processed' in resp.text
assert source_url in resp.text
def test_transaction_status_api(app, regie, user):
# invalid transaction signature
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps('xxxx')}
)
resp = app.get(url, status=404)
assert 'Unknown transaction.' in resp.text
# unkown transaction identifier
transaction_id = 1000
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction_id)}
)
resp = app.get(url, status=404)
assert 'Unknown transaction.' in resp.text
wait_response = {
'wait': True,
'error': False,
'error_msg': ''
}
# anonymous user on anonymous transaction: OK
transaction = Transaction.objects.create(amount=Decimal('10.0'), regie=regie, status=0)
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction.pk)}
)
resp = app.get(url)
assert resp.json == wait_response
# authenticated user on anonymous transaction: OK
transaction = Transaction.objects.create(amount=Decimal('10.0'), regie=regie, status=0)
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction.pk)}
)
resp = login(app).get(url)
assert resp.json == wait_response
app.reset()
# authenticated user on his transaction: OK
transaction = Transaction.objects.create(
amount=Decimal('10.0'), regie=regie, status=0, user=user)
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction.pk)}
)
resp = login(app).get(url)
assert resp.json == wait_response
app.reset()
error_msg = 'Transaction does not belong to the requesting user'
# anonymous user on other user transaction transaction: NOTOK
transaction = Transaction.objects.create(
amount=Decimal('10.0'), regie=regie, status=0, user=user)
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction.pk)}
)
resp = app.get(url, status=403)
assert error_msg in resp.text
# authenticated user on other user transaction transaction: NOTOK
user2 = User.objects.create_user(
'user2', password='user2', email='user2@example.com'
)
transaction = Transaction.objects.create(amount=Decimal('10.0'), regie=regie, status=0, user=user2)
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction.pk)}
)
resp = login(app).get(url, status=403)
assert error_msg in resp.text
app.reset()
# transaction error
transaction = Transaction.objects.create(
amount=Decimal('10.0'), regie=regie, status=eopayment.ERROR
)
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction.pk)}
)
resp = app.get(url)
assert resp.json == {
'wait': True,
'error': True,
'error_msg': 'Payment error, you can continue and make another payment'
}
# transaction paid
transaction = Transaction.objects.create(
amount=Decimal('10.0'), regie=regie, status=eopayment.PAID
)
url = reverse(
'api-transaction-status',
kwargs={'transaction_signature': signing_dumps(transaction.pk)}
)
resp = app.get(url)
assert resp.json == {
'wait': False,
'error': False,
'error_msg': ''
}
def test_request_payment_exception(app, basket_page, regie, user):
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
with mock.patch('eopayment.dummy.Payment.request', autospec=True) as mock_request:
mock_request.side_effect = eopayment.PaymentException
resp = login(app).get(basket_page.get_online_url())
resp = resp.form.submit().follow()
assert 'Failed to initiate payment request' in resp.text