tipi payment form cell (#14519)

This commit is contained in:
Serghei Mihai 2017-02-28 11:59:36 +01:00
parent a5309c31af
commit 970fadf03b
5 changed files with 291 additions and 1 deletions

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
('data', '0022_auto_20170214_2006'),
('lingo', '0027_auto_20170214_2006'),
]
operations = [
migrations.CreateModel(
name='TipiPaymentFormCell',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('placeholder', models.CharField(max_length=20)),
('order', models.PositiveIntegerField()),
('slug', models.SlugField(verbose_name='Slug', blank=True)),
('extra_css_class', models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True)),
('public', models.BooleanField(default=True, verbose_name='Public')),
('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
('last_update_timestamp', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=150, verbose_name='Title', blank=True)),
('url', models.URLField(default=b'https://www.tipi.budget.gouv.fr/tpa/paiement.web', verbose_name='TIPI payment service URL')),
('regies', models.CharField(help_text='separated by commas', max_length=256, verbose_name='Regies')),
('control_protocol', models.CharField(default=b'1', max_length=8, verbose_name='Control protocol', choices=[(b'pesv2', 'Indigo/PES v2'), (b'rolmre', 'ROLMRE')])),
('test_mode', models.BooleanField(default=False, verbose_name='Test mode')),
('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
('page', models.ForeignKey(to='data.Page')),
],
options={
'verbose_name': 'TIPI Payment Form',
},
bases=(models.Model,),
),
]

View File

@ -500,3 +500,38 @@ class SelfDeclaredInvoicePayment(Items):
context['synchronous'] = True
context['page_path'] = context['request'].path
return super(Items, self).render(context)
TIPI_CONTROL_PROCOTOLS = (
('pesv2', _('Indigo/PES v2')),
('rolmre', _('ROLMRE')),
)
@register_cell_class
class TipiPaymentFormCell(CellBase):
title = models.CharField(_('Title'), max_length=150, blank=True)
url = models.URLField(_('TIPI payment service URL'), default='https://www.tipi.budget.gouv.fr/tpa/paiement.web')
regies = models.CharField(_('Regies'), help_text=_('separated by commas'), max_length=256)
control_protocol = models.CharField(_('Control protocol'), max_length=8, choices=TIPI_CONTROL_PROCOTOLS, default='1')
test_mode = models.BooleanField(_('Test mode'), default=False)
template_name = 'lingo/tipi_form.html'
class Meta:
verbose_name = _('TIPI Payment Form')
class Media:
js = ('js/tipi.js',)
def get_cell_extra_context(self, context):
extra_context = super(TipiPaymentFormCell, self).get_cell_extra_context(context)
context['title'] = self.title
context['url'] = self.url
context['mode'] = 'T' if self.test_mode else 'M'
context['pesv2'] = (self.control_protocol == 'pesv2')
context['regies'] = []
for regie in self.regies.split(','):
regie_id = regie.strip()
if not regie_id:
continue
context['regies'].append(regie_id)
return extra_context

View File

@ -0,0 +1,49 @@
{% load i18n %}
<h2>{{ title }}</h2>
<div>
<form name="tipi_form" id="tipi_form" data-url="{{ url }}" data-saisie="{{ mode }}" data-pesv2="{{ pesv2 }}">
<ul class="errorlist" id="popup_warning" style="display: none">
<li>{% trans "Your browser is blocking popups but they are required to start the payment, make sure they are allowed for this site." %}</li>
</ul>
<p><label>{% trans "Community identifier" %}</label>
<select id="numcli">
{% for id in regies %}
<option value="{{ id }}">{{ id }}</option>
{% endfor %}
</select>
</p>
<ul class="errorlist" id="refdet_error" style="display: none">
<li>{% trans "invalid reference" %}</li>
</ul>
<p>
<label>{% trans "Reference" %}</label>
<input type="text" id="exer" maxlength="4" size="4" placeholder="0000" /> -
{% if pesv2 %}
<input type="text" id="idpce" maxlength="8" size="8" placeholder="00000000" /> -
<input type="text" id="idligne" maxlength="6" size="6" placeholder="000000" />
{% else %}
<input type="text" id="rolrec" maxlength="2" size="2" placeholder="00" /> -
<input type="text" id="roldeb" maxlength="2" size="2" placeholder="00" /> -
<input type="text" id="roldet" maxlength="13" size="13" placeholder="0000000000000" />
{% endif %}
</p>
<ul class="errorlist" id="montant_error" style="display: none">
<li>{% trans "invalid amount" %}</li>
</ul>
<p>
<label>{% trans "Amount" %}</label>
<input type="text" id="montant_euros" size="4" maxlength="4" placeholder="0000" /> ,
<input type="text" id="montant_cents" size="2" maxlength="2" placeholder="00" value="00" />
</p>
<ul class="errorlist" id="mel_error" style="display: none">
<li>{% trans "invalid email" %}</li>
</ul>
<p>
<label>{% trans "Email" %}</label>
<input type="email" id="mel" {% if request.user.email %}value="{{ request.user.email }}" {% endif %} placeholder="{% trans "user@example.net" %}" />
</p>
<p>
<button>{% trans "Pay" %}</button>
</p>
</form>
</div>

View File

@ -0,0 +1,129 @@
$(function() {
var tipi_popup;
var tipi_timer;
function reinit() {
$('#refdet_error').hide();
$('#montant_error').hide();
$('#mel_error').hide();
}
function checkpopup() {
// checks if TIPI is still open and disable form widgets
if (tipi_popup.closed) {
$('#tipi_form input, #tipi_form select, #tipi_form button').attr('disabled', false);
document.forms['tipi_form'].reset();
clearInterval(tipi_timer);
}
}
function refdet_valid_value(element) {
var value = element.val().trim();
if (!value || isNaN(value)) {
$('#refdet_error').show();
return false;
}
return true;
}
function fill_padding(element) {
// if element is shorter than required length it should be padded with zeros
var padding = '';
var value = element.val().trim();
for (var i=0; i<element.attr('maxLength')-value.length; i++) {
padding += '0';
}
return padding + value;
}
$("form#tipi_form").on("submit", function() {
var url = $("form#tipi_form").data("url");
var saisie = $("form#tipi_form").data("saisie");
var pesv2 = $("form#tipi_form").data("pesv2") == 'True';
var params = {'refdet': function(id) {
var exer = params.exer('exer');
if (pesv2) {
// if PESv2 then refdet is composed by exer + idpce + idligne
var idpce = $('#idpce');
var idligne = $('#idligne');
if (refdet_valid_value(idpce) && refdet_valid_value(idligne)) {
refdet = fill_padding(idpce) + fill_padding(idligne);
} else {
return;
}
} else {
// if ROLMRE then refdet is composed by exer + rolrec + roldeb + roldet
var rolrec = $('#rolrec');
var roldeb = $('#roldeb');
var roldet = $('#roldet');
if (refdet_valid_value(rolrec) && refdet_valid_value(roldeb) && refdet_valid_value(roldet)) {
refdet = fill_padding(rolrec) + fill_padding(roldeb) + fill_padding(roldet);
} else {
return;
}
}
if (saisie == 'T') {
// in test mode the refdet should be 999900000000999999
return "999900000000999999";
} else {
return exer + refdet;
}
}, 'montant': function(id) {
var euros = $('#' + id + '_euros').val().trim();
var cents = $('#' + id + '_cents').val().trim();
if (euros && cents) {
if (isNaN(euros) || isNaN(cents)) {
$('#' + id + '_error').show();
return false;
}
return parseInt(euros) * 100 + parseInt(cents);
}
$('#' + id + '_error').show();
return false;
}, 'mel': function(id) {
var email = $('#' + id).val().trim();
if (!email) {
$('#' + id +'_error').show();
}
return email;
}, 'exer': function(id) {
if (saisie == 'T') {
// in test mode exer should be 9999
return "9999";
}
var exer = $('#' + id);
if (refdet_valid_value(exer)) {
return exer.val().trim();
}
return false;
}};
var tipi_url = url + '?saisie=' + saisie + '&numcli=' + $('#numcli').val();
var url_params = '&';
reinit();
for (var field in params) {
var valid = params[field](field);
if (!valid) {
return false;
} else {
var field_error = $('#' + field + '_error');
if (field_error) {
field_error.hide();
}
url_params += field + '=' + valid + '&';
}
}
var url = tipi_url;
url = tipi_url + encodeURI(url_params);
// TIPI popup requires specific params
tipi_popup = window.open(url, 'tipi', 'height=800, width=900, toolbar=no, menubar=no, scrollbars=no, resizable=yes, location=no, directories=no, status=no');
if (tipi_popup) {
$('#tipi_form input, #tipi_form select, #tipi_form button').attr('disabled', true);
tipi_timer = setInterval(checkpopup, 400);
$('#popup_warning').hide();
} else {
$('#popup_warning').show();
}
return false;
});
});

View File

@ -11,7 +11,7 @@ from django.utils import timezone
from combo.data.models import Page
from combo.apps.lingo.models import Regie, BasketItem, Transaction
from combo.apps.lingo.models import (LingoBasketCell,
LingoRecentTransactionsCell, LingoBasketLinkCell)
LingoRecentTransactionsCell, LingoBasketLinkCell, TipiPaymentFormCell)
pytestmark = pytest.mark.django_db
@ -152,3 +152,40 @@ def test_basket_link_cell(regie, user):
content = cell.render(context)
assert '12345' in content
assert page.get_online_url() in content
def test_tipi_cell():
page = Page(title='example page', slug='example-page')
page.save()
cell = TipiPaymentFormCell()
cell.page = page
cell.title = 'TIPI Payment'
cell.regies = "test regie"
cell.order = 0
cell.save()
assert cell.control_protocol == '1'
assert cell.url == 'https://www.tipi.budget.gouv.fr/tpa/paiement.web'
assert cell.template_name == 'lingo/tipi_form.html'
html = cell.render(Context({}))
assert "<h2>TIPI Payment</h2>" in html
assert "Community identifier" in html
assert 'id="exer"' in html
assert 'id="idpce"' in html
assert 'id="idligne"' in html
assert 'id="rolrec"' not in html
assert 'id="roldeb"' not in html
assert 'id="roldet"' not in html
assert 'data-saisie="M"' in html
cell.control_protocol = '2'
cell.test_mode = True
cell.save()
html = cell.render(Context({}))
assert 'id="rolrec"' in html
assert 'id="roldeb"' in html
assert 'id="roldet"' in html
assert 'id="idpce"' not in html
assert 'id="idligne"' not in html
assert 'data-saisie="T"' in html
cell_media = str(cell.media)
assert "js/tipi.js" in cell_media