lingo: start transaction options (#32967)

Based on the backend capabilities.
This commit is contained in:
Emmanuel Cazenave 2019-05-14 16:38:32 +02:00
parent 2eaeeb3670
commit 23f13059d0
7 changed files with 180 additions and 13 deletions

69
combo/apps/lingo/forms.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

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

View File

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

View File

@ -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 != ''

View File

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

View File

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

View File

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