From 23f13059d00a761af113a09b92d0031f9648cddb Mon Sep 17 00:00:00 2001 From: Emmanuel Cazenave Date: Tue, 14 May 2019 16:38:32 +0200 Subject: [PATCH] lingo: start transaction options (#32967) Based on the backend capabilities. --- combo/apps/lingo/forms.py | 69 +++++++++++++++++++ combo/apps/lingo/manager_views.py | 14 +--- .../0037_regie_transaction_options.py | 21 ++++++ combo/apps/lingo/models.py | 4 ++ combo/apps/lingo/views.py | 2 + tests/test_lingo_manager.py | 57 ++++++++++++++- tests/test_lingo_payment.py | 26 +++++++ 7 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 combo/apps/lingo/forms.py create mode 100644 combo/apps/lingo/migrations/0037_regie_transaction_options.py diff --git a/combo/apps/lingo/forms.py b/combo/apps/lingo/forms.py new file mode 100644 index 00000000..f19573ab --- /dev/null +++ b/combo/apps/lingo/forms.py @@ -0,0 +1,69 @@ +# lingo - basket and payment system +# Copyright (C) 2019 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from django import forms + +from .models import Regie + + +def create_form_fields(parameters, json_field): + fields, initial = {}, {} + for param in parameters: + field_name = param['name'] + if param['type'] is bool: + fields[field_name] = forms.BooleanField(label=param['caption'], required=False) + initial[field_name] = param['default'] + if field_name in json_field: + initial[field_name] = json_field[field_name] + else: + raise NotImplementedError() + return fields, initial + + +def compute_json_field(parameters, cleaned_data): + json_field = {} + for param in parameters: + param_name = param['name'] + if param_name in cleaned_data: + json_field[param_name] = cleaned_data[param_name] + return json_field + + +class RegieForm(forms.ModelForm): + + class Meta: + model = Regie + fields = ['label', 'slug', 'description', 'payment_backend', 'is_default', + 'webservice_url', 'extra_fees_ws_url', 'payment_min_amount', 'text_on_success'] + + def __init__(self, *args, **kwargs): + super(RegieForm, self).__init__(*args, **kwargs) + fields, initial = create_form_fields( + self.instance.payment_backend.get_payment().get_parameters(scope='transaction'), + self.instance.transaction_options + ) + self.fields.update(fields) + self.initial.update(initial) + + def save(self): + instance = super(RegieForm, self).save() + instance.transaction_options = compute_json_field( + self.instance.payment_backend.get_payment().get_parameters(scope='transaction'), + self.cleaned_data + ) + instance.save() + return instance diff --git a/combo/apps/lingo/manager_views.py b/combo/apps/lingo/manager_views.py index ed2afb41..d9a8e016 100644 --- a/combo/apps/lingo/manager_views.py +++ b/combo/apps/lingo/manager_views.py @@ -27,31 +27,23 @@ from django.http import HttpResponse import eopayment +from .forms import RegieForm from .models import PaymentBackend, Regie, Transaction -REGIE_FIELDS = ['label', 'slug', 'description', 'payment_backend', 'is_default', 'webservice_url', - 'extra_fees_ws_url', 'payment_min_amount', 'text_on_success'] - - class RegieListView(ListView): model = Regie class RegieCreateView(CreateView): model = Regie - fields = REGIE_FIELDS + fields = ['label', 'slug', 'description', 'payment_backend'] success_url = reverse_lazy('lingo-manager-regie-list') - def get_initial(self): - if self.model.objects.all().count() == 0: - return {'is_default': True} - return {} - class RegieUpdateView(UpdateView): model = Regie - fields = REGIE_FIELDS + form_class = RegieForm success_url = reverse_lazy('lingo-manager-regie-list') diff --git a/combo/apps/lingo/migrations/0037_regie_transaction_options.py b/combo/apps/lingo/migrations/0037_regie_transaction_options.py new file mode 100644 index 00000000..1633d1b3 --- /dev/null +++ b/combo/apps/lingo/migrations/0037_regie_transaction_options.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-05-14 12:02 +from __future__ import unicode_literals + +from django.db import migrations +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('lingo', '0036_auto_20190426_1202'), + ] + + operations = [ + migrations.AddField( + model_name='regie', + name='transaction_options', + field=jsonfield.fields.JSONField(blank=True, default=dict, verbose_name='Transaction Options'), + ), + ] diff --git a/combo/apps/lingo/models.py b/combo/apps/lingo/models.py index a9c700d0..f4be4fc7 100644 --- a/combo/apps/lingo/models.py +++ b/combo/apps/lingo/models.py @@ -98,6 +98,9 @@ class PaymentBackend(models.Model): def __str__(self): return self.label + def get_payment(self): + return eopayment.Payment(self.service, self.service_options) + @python_2_unicode_compatible class Regie(models.Model): @@ -117,6 +120,7 @@ class Regie(models.Model): verbose_name=_('Custom text displayed on success'), blank=True, null=True) payment_backend = models.ForeignKey(PaymentBackend, on_delete=models.CASCADE) + transaction_options = JSONField(blank=True, verbose_name=_('Transaction Options')) def is_remote(self): return self.webservice_url != '' diff --git a/combo/apps/lingo/views.py b/combo/apps/lingo/views.py index cb4f0895..4150d79b 100644 --- a/combo/apps/lingo/views.py +++ b/combo/apps/lingo/views.py @@ -339,6 +339,8 @@ class PayMixin(object): capture_date = items[0].capture_date if capture_date: kwargs['capture_date'] = capture_date + if regie.transaction_options: + kwargs.update(regie.transaction_options) (order_id, kind, data) = payment.request(total_amount, **kwargs) logger = logging.getLogger(__name__) logger.info(u'emitted payment request with id %s', smart_text(order_id), extra={ diff --git a/tests/test_lingo_manager.py b/tests/test_lingo_manager.py index 44c1f17e..b2268034 100644 --- a/tests/test_lingo_manager.py +++ b/tests/test_lingo_manager.py @@ -47,7 +47,6 @@ def test_add_regie(app, admin_user, payment_backend): resp.forms[0]['slug'] = 'test' resp.forms[0]['description'] = 'description' resp.forms[0]['payment_backend'] = payment_backend.pk - assert resp.form['is_default'].checked is True resp = resp.forms[0].submit() assert resp.location.endswith('/manage/lingo/regies/') assert Regie.objects.count() == 1 @@ -68,6 +67,61 @@ def test_edit_regie(app, admin_user, payment_backend): regie = Regie.objects.all()[0] assert regie.description == 'other description' + +def test_edit_regie_dynamic_backend_fields(app, admin_user): + payment_backend = PaymentBackend.objects.create( + label='test1', slug='test1', service='systempayv2', service_options={'siret': '1234'}) + regie = Regie.objects.create( + label='test-regie', slug='test-regie', payment_backend=payment_backend) + assert regie.transaction_options == {} + + app = login(app) + resp = app.get('/manage/lingo/regies/%s/edit' % regie.pk, status=200) + resp.forms[0]['label'] = 'test-regie' + resp.forms[0]['slug'] = 'test-regie' + resp.forms[0]['description'] = 'description' + resp.forms[0]['payment_backend'] = payment_backend.pk + + assert resp.forms[0]['manual_validation'].checked is False + resp.forms[0]['manual_validation'] = True + resp = resp.forms[0].submit() + + assert Regie.objects.count() == 1 + + regie = Regie.objects.get(slug='test-regie') + assert regie.transaction_options['manual_validation'] + + # No dynamic fields if no backend capabilities + payment_backend_dummy = PaymentBackend.objects.create( + label='test1', slug='test-dummy', service='dummy', service_options={'siret': '1234'}) + regie_dummy = Regie.objects.create( + label='test-regie-2', slug='test-regie-2', payment_backend=payment_backend_dummy) + + resp = app.get('/manage/lingo/regies/%s/edit' % regie_dummy.pk, status=200) + assert 'manual_validation' not in resp.forms[0].fields + resp.forms[0]['label'] = 'Test' + resp.forms[0]['slug'] = 'test-regie-2' + resp.forms[0]['description'] = 'description' + resp.forms[0]['payment_backend'] = payment_backend_dummy.pk + resp = resp.forms[0].submit() + + regie_dummy = Regie.objects.get(slug='test-regie-2') + assert regie_dummy.transaction_options == {} + + # Change backend + resp = app.get('/manage/lingo/regies/%s/edit' % regie.pk, status=200) + resp.forms[0]['label'] = 'test-regie' + resp.forms[0]['slug'] = 'test-regie' + resp.forms[0]['description'] = 'description' + resp.forms[0]['payment_backend'] = payment_backend_dummy.pk + + assert resp.forms[0]['manual_validation'].checked is True + resp.forms[0]['manual_validation'] = True + resp = resp.forms[0].submit() + + regie = Regie.objects.get(slug='test-regie') + assert regie.transaction_options == {} + def test_delete_regie(app, admin_user, payment_backend): test_add_regie(app, admin_user, payment_backend) app = login(app) @@ -88,7 +142,6 @@ def test_add_second_regie(app, admin_user, payment_backend): resp.forms[0]['slug'] = 'test2' resp.forms[0]['description'] = 'description' resp.forms[0]['payment_backend'] = payment_backend.pk - assert resp.form['is_default'].checked is False resp = resp.forms[0].submit() assert resp.location.endswith('/manage/lingo/regies/') diff --git a/tests/test_lingo_payment.py b/tests/test_lingo_payment.py index 45666ce5..902578b4 100644 --- a/tests/test_lingo_payment.py +++ b/tests/test_lingo_payment.py @@ -136,6 +136,32 @@ def test_payment_min_amount(app, basket_page, regie, user): 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.kwargs['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'},