lingo: add possibility to compute extra fees (#16065)
This commit is contained in:
parent
a013add9ec
commit
23b7b294eb
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import jsonfield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lingo', '0028_tipipaymentformcell'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='basketitem',
|
||||
options={'ordering': ['regie', 'extra_fee', 'subject']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='basketitem',
|
||||
name='extra_fee',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='basketitem',
|
||||
name='request_data',
|
||||
field=jsonfield.fields.JSONField(default=dict, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='regie',
|
||||
name='extra_fees_ws_url',
|
||||
field=models.URLField(verbose_name='Webservice URL to compute extra fees', blank=True),
|
||||
),
|
||||
]
|
|
@ -83,6 +83,8 @@ class Regie(models.Model):
|
|||
is_default = models.BooleanField(verbose_name=_('Default Regie'), default=False)
|
||||
webservice_url = models.URLField(_('Webservice URL to retrieve remote items'),
|
||||
blank=True)
|
||||
extra_fees_ws_url = models.URLField(_('Webservice URL to compute extra fees'),
|
||||
blank=True)
|
||||
payment_min_amount = models.DecimalField(_('Minimal payment amount'),
|
||||
max_digits=7, decimal_places=2, default=0)
|
||||
|
||||
|
@ -167,6 +169,39 @@ class Regie(models.Model):
|
|||
'text': self.label,
|
||||
'description': self.description}
|
||||
|
||||
def compute_extra_fees(self, user):
|
||||
if not self.extra_fees_ws_url:
|
||||
return
|
||||
post_data = {'data': []}
|
||||
basketitems = BasketItem.objects.filter(
|
||||
user=user, regie=self,
|
||||
cancellation_date__isnull=True,
|
||||
payment_date__isnull=True)
|
||||
for basketitem in basketitems.filter(extra_fee=False):
|
||||
basketitem_data = {
|
||||
'subject': basketitem.subject,
|
||||
'source_url': basketitem.source_url,
|
||||
'details': basketitem.details,
|
||||
'amount': basketitem.amount,
|
||||
'request_data': basketitem.request_data
|
||||
}
|
||||
post_data['data'].append(basketitem_data)
|
||||
if not post_data['data']:
|
||||
basketitems.filter(extra_fee=True).delete()
|
||||
return
|
||||
response = requests.post(self.extra_fees_ws_url, remote_service='auto')
|
||||
if response.status_code != 200 or response.json().get('err'):
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error('failed to compute extra fees (user: %r)', user)
|
||||
return
|
||||
basketitems.filter(extra_fee=True).delete()
|
||||
for extra_fee in response.json().get('data'):
|
||||
BasketItem(user=user, regie=self,
|
||||
subject=extra_fee.get('subject'),
|
||||
amount=extra_fee.get('amount'),
|
||||
extra_fee=True,
|
||||
user_cancellable=False).save()
|
||||
|
||||
|
||||
class BasketItem(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
|
||||
|
@ -176,13 +211,20 @@ class BasketItem(models.Model):
|
|||
details = models.TextField(verbose_name=_('Details'), blank=True)
|
||||
amount = models.DecimalField(verbose_name=_('Amount'),
|
||||
decimal_places=2, max_digits=8)
|
||||
request_data = JSONField(blank=True)
|
||||
extra_fee = models.BooleanField(default=False)
|
||||
user_cancellable = models.BooleanField(default=True)
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
cancellation_date = models.DateTimeField(null=True)
|
||||
payment_date = models.DateTimeField(null=True)
|
||||
notification_date = models.DateTimeField(null=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['regie', 'extra_fee', 'subject']
|
||||
|
||||
def notify(self, status):
|
||||
if not self.source_url:
|
||||
return
|
||||
url = self.source_url + 'jump/trigger/%s' % status
|
||||
message = {'result': 'ok'}
|
||||
if status == 'paid':
|
||||
|
@ -200,12 +242,14 @@ class BasketItem(models.Model):
|
|||
self.notify('paid')
|
||||
self.notification_date = timezone.now()
|
||||
self.save()
|
||||
self.regie.compute_extra_fees(user=self.user)
|
||||
|
||||
def notify_cancellation(self, notify_origin=False):
|
||||
if notify_origin:
|
||||
self.notify('cancelled')
|
||||
self.cancellation_date = timezone.now()
|
||||
self.save()
|
||||
self.regie.compute_extra_fees(user=self.user)
|
||||
|
||||
@property
|
||||
def total_amount(self):
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<input type="hidden" name="next_url" value="{{ cell.page.get_online_url }}" />
|
||||
<ul>
|
||||
{% for item in regie_info.items %}
|
||||
<li><a href="{{ item.source_url }}">{{ item.subject }}</a>: {{ item.amount }} €
|
||||
<li><a {% if item.source_url %}href="{{ item.source_url }}{% endif %}">{{ item.subject }}</a>: {{ item.amount }} €
|
||||
{% if item.user_cancellable %}
|
||||
<a rel="popup" href="{% url 'lingo-cancel-item' pk=item.id %}">({% trans 'remove' %})</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -114,6 +114,11 @@ class AddBasketItemApiView(View):
|
|||
if extra.get('amount'):
|
||||
item.amount += self.get_amount(extra['amount'])
|
||||
|
||||
if 'extra' in request_body:
|
||||
item.request_data = request_body.get('extra')
|
||||
else:
|
||||
item.request_data = request_body
|
||||
|
||||
try:
|
||||
if request.GET.get('NameId'):
|
||||
if UserSAMLIdentifier is None:
|
||||
|
@ -153,6 +158,7 @@ class AddBasketItemApiView(View):
|
|||
item.source_url = request_body.get('url') or ''
|
||||
|
||||
item.save()
|
||||
item.regie.compute_extra_fees(user=item.user)
|
||||
|
||||
response = HttpResponse(content_type='application/json')
|
||||
response.write(json.dumps({'result': 'success', 'id': str(item.id)}))
|
||||
|
@ -297,6 +303,7 @@ class PayView(View):
|
|||
remote_items_data.append(regie.get_invoice(request.user, item_id))
|
||||
remote_items = ','.join([x.id for x in remote_items_data])
|
||||
else:
|
||||
regie.compute_extra_fees(user=self.request.user)
|
||||
items = BasketItem.objects.filter(user=self.request.user,
|
||||
regie=regie, payment_date__isnull=True,
|
||||
cancellation_date__isnull=True)
|
||||
|
@ -427,6 +434,7 @@ class CallbackView(View):
|
|||
except RuntimeError:
|
||||
# ignore errors, it should be retried later on if it fails
|
||||
pass
|
||||
regie.compute_extra_fees(user=transaction.user)
|
||||
if transaction.remote_items:
|
||||
for item_id in transaction.remote_items.split(','):
|
||||
remote_item = regie.get_invoice(user=transaction.user, invoice_id=item_id)
|
||||
|
|
|
@ -191,13 +191,14 @@ def test_add_amount_to_basket(key, regie, user):
|
|||
|
||||
url = '%s?email=%s®ie_id=%s' % (
|
||||
reverse('api-add-basket-item'), user_email, regie.id)
|
||||
data['extra'] = {'amount': '22.24'}
|
||||
data['extra'] = {'amount': '22.24', 'foo': 'bar'}
|
||||
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
|
||||
resp = client.post(url, json.dumps(data), content_type='application/json')
|
||||
assert resp.status_code == 200
|
||||
assert json.loads(resp.content)['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®ie_id=%s' % (
|
||||
reverse('api-add-basket-item'), user_email, regie.slug)
|
||||
|
@ -446,3 +447,74 @@ def test_transaction_cancel(key, regie, user):
|
|||
resp = client.post(url, content_type='application/json')
|
||||
assert json.loads(resp.content)['err'] == 1
|
||||
assert TransactionOperation.objects.filter(transaction=t1).count() == 1
|
||||
|
||||
def test_extra_fees(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.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 = client.post(url, json.dumps(data), content_type='application/json')
|
||||
assert resp.status_code == 200
|
||||
assert json.loads(resp.content)['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.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 = client.post(url, json.dumps(data), content_type='application/json')
|
||||
assert resp.status_code == 200
|
||||
assert json.loads(resp.content)['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.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 = client.post(url, json.dumps(data), content_type='application/json')
|
||||
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
|
||||
login()
|
||||
|
||||
with mock.patch('combo.utils.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 = client.post(reverse('lingo-pay'), {'regie': regie.pk})
|
||||
assert resp.status_code == 302
|
||||
location = resp.get('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'
|
||||
|
||||
# call callback with GET
|
||||
callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id})
|
||||
resp = client.get(callback_url, data)
|
||||
assert resp.status_code == 200
|
||||
assert Transaction.objects.get(order_id=transaction_id).status == 3
|
||||
|
|
Loading…
Reference in New Issue