]
}
-# Sherlock's online payment
-PAYMENT_SHERLOCKS_PATH = '/opt/sherlocks2'
-PAYMENT_PARAMETERS = {'merchant_id': {'Semestrielle': "040109417200053",
- 'Estivale': "040109417200054", },
- 'merchant_country': 'fr',
- 'currency_code': '978',
- 'language': 'fr'
+SHERLOKS_URL = "https://sherlocks-payment-webinit-simu.secure.lcl.fr/paymentInit"
+
+PAYMENT_PARAMETERS = { 'merchantId' : "002001000000003",
+ '_key': "002001000000003_KEY1",
+ 'keyVersion': '1',
+ 'currencyCode': '978',
}
+SHERLOKS_USE_TRANSACTION_ID = True
LOGGING = {
'version': 1,
from teleforma.models import *
import logging
import datetime
+from django.core.exceptions import ObjectDoesNotExist
class Logger:
"""A logging object"""
help = "Send emails to students that must pay"
language_code = 'fr_FR'
+ def add_arguments(self, parser):
+ parser.add_argument('log_file')
+
def email(self, student, kind, payment):
site = Site.objects.get_current()
- ctx_dict = {'site': site, 'organization': settings.TELEFORMA_ORGANIZATION, 'student': student, 'payment': payment, 'period': student.period }
+ ctx_dict = {'site': site, 'organization': settings.TELEFORMA_ORGANIZATION, 'student': student, 'payment': payment }
subject_template = 'payment/email_%s_subject.txt' % kind
message_template = 'payment/email_%s.txt' % kind
subject = render_to_string(subject_template, ctx_dict)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [student.user.email], fail_silently=False)
def handle(self, *args, **options):
- log_file = args[-1]
+ log_file = options['log_file']
logger = Logger(log_file)
logger.logger.info('########### Processing #############')
translation.activate(self.language_code)
today = datetime.date.today()
-
+
for student in students:
- if student.is_subscribed and student.user.email:
+ try:
+ user = student.user
+ except ObjectDoesNotExist:
+ logger.logger.warning('missing user object for %s' % student.pk)
+ continue
+
+ if student.is_subscribed and user.email and user.is_active :
for payment in student.payments.filter(type = 'online').exclude(online_paid = True).filter(scheduled__lte = today):
if payment.scheduled == today:
self.email(student, 'initial', payment)
- logger.logger.info('initial email send for %s on %d' % (student.user.username, payment.id))
+ logger.logger.info('initial email send for %s on %d' % (user.username, payment.id))
else:
delta = today - payment.scheduled
delta = delta.days
if delta % 7 == 0:
self.email(student, 'reminder', payment)
- logger.logger.info('reminder email send for %s on %d' % (student.user.username, payment.id))
+ logger.logger.info('reminder email send for %s on %d' % (user.username, payment.id))
+
logger.logger.info('############## Done #################')
+++ /dev/null
-{% load admin_static %}{% load suit_tags %}{% load url from future %}<!DOCTYPE html>
-<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
-<head>
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <title>{% block title %}{{ title }} | {{ 'ADMIN_NAME'|suit_conf }}{% endblock %}</title>
- <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% endblock %}"/>
- <link rel="stylesheet" type="text/css" href="{% static 'suit/bootstrap/css/bootstrap.min.css' %}" media="all"/>
- <link rel="stylesheet" type="text/css" href="{% static 'suit/css/suit.css' %}" media="all">
- <link rel="stylesheet" type="text/css" href="{% static 'teleforma/css/admin.css' %}" media="all">
- {% block extrastyle %}{% endblock %}
- {% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}"/>{% endif %}
- <script type="text/javascript">window.__admin_media_prefix__ = "{% filter escapejs %}{% static "admin/" %}{% endfilter %}";</script>
- <script src="{% static 'suit/js/jquery-1.8.3.min.js' %}"></script>
- <script type="text/javascript">var Suit = { $: $.noConflict() }; if (!$) $ = Suit.$; </script>
- <script src="{% static 'suit/bootstrap/js/bootstrap.min.js' %}"></script>
- {% if 'SHOW_REQUIRED_ASTERISK'|suit_conf %}
- <style type="text/css">.required:after { content: '*'; margin: 0 0 0 5px; position: absolute; color: #ccc;}</style>
- {% endif %}
- {% block extrahead %}{% endblock %}
- {% block blockbots %}
- <meta name="robots" content="NONE,NOARCHIVE"/>{% endblock %}
-
-<script type="text/javascript">$(document).ready(function () {
- /* fix issue https://trackers.pilotsystems.net/probarreau/0481 */
- $(".field-trainings select").each(function () {
- $(this).val($(this).find('option[selected]').val());
- });
-})</script>
-</head>
-{% load i18n %}
-
-<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}">
-
-<!-- Sticky footer wrap -->
-<div id="wrap">
-
- <!-- Container -->
- {% block container %}
- <div id="container">
-
- {% block header %}
- {% if not is_popup %}
- <!-- Header -->
- <div id="header" class="header">
- <div id="branding">
- <a href="{% url 'admin:index' %}"><h1 id="site-name">{% block branding %}{{ 'ADMIN_NAME'|suit_conf }}{% endblock %}</h1></a>
- </div>
-
- {% block header_time %}
- <div class="header-content header-content-first">
- <div class="header-column icon">
- <i class="icon-time"></i>
- </div>
- <div class="header-column">
- <span class="date"> {% suit_date %}</span><br>
- <span class="time" id="clock">{% suit_time %}</span>
- </div>
- </div>
- {% endblock %}
-
- {% block header_content %}
- <!--<div class="header-content">
- <div class="header-column icon">
- <i class="icon-comment"></i>
- </div>
- <div class="header-column">
- <a href="" class="grey"><b>2</b> new messages</a>
- </div>
- </div>-->
- {% endblock %}
-
- {% if user.is_active and user.is_staff %}
- <div id="user-tools">
- {% trans 'Welcome,' %}
- <strong>
- {% filter force_escape %}
- {% firstof user.first_name user.username %}{% endfilter %}</strong>.
- <span class="user-links">
- {% block userlinks %}
- {% url 'django-admindocs-docroot' as docsroot %}
- {% if docsroot %}
- <a href="{{ docsroot }}">{% trans 'Documentation' %}</a>
- <span class="separator">|</span>
- {% endif %}
- <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a>
- <span class="separator">|</span>
- <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
- </span>
- {% endblock %}
- </div>
- {% endif %}
- {% block nav-global %}{% endblock %}
- </div>
- {% endif %}
- <!-- END Header -->
- {% endblock %}
-
-
- <div class="suit-columns {{ is_popup|yesno:'one-column,two-columns' }}">
-
- {% block content-center %}
- <div id="suit-center" class="suit-column">
-
- {% if not is_popup %}
- {% block breadcrumbs %}
- <ul class="breadcrumb">
- <li><a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
- {% if title %}
- <span class="divider">»</span>
- </li>
- <li class="active">
- {{ title }}
- {% endif %}
- </li>
- </ul>
- {% endblock %}
- {% endif %}
-
- {% block messages %}
- {% if messages %}
-
- {% for message in messages %}
- <div class="alert alert-{% firstof message.tags 'info' %}">
- <button class="close" data-dismiss="alert">×</button>
- {{ message }}
- </div>
- {% endfor %}
- {% endif %}
- {% endblock messages %}
-
- <!-- Content -->
- <div id="content" class="{% block coltype %}colM{% endblock %} row-fluid">
- {% block pretitle %}{% endblock %}
- {% block content_title %}{% if title %}
- <h2 class="content-title">{{ title }}</h2>
- {% endif %}{% endblock %}
- {% block content %}
- {% block object-tools %}{% endblock %}
- {{ content }}
- {% endblock %}
- {% block sidebar_content %}
- {% block sidebar %}{% endblock %}
- {% endblock %}
- </div>
- <!-- END Content -->
- </div>
- {% endblock %}
-
-
- {% block content-left %}
- {% if not is_popup %}
- <div id="suit-left" class="suit-column">
- {% block quick-search %}
- {% with 'SEARCH_URL'|suit_conf as search_url %}
- {% if search_url %}
- <form class="form-search nav-quick-search" autocomplete="off" action="{% if '/' in search_url %}{{ search_url }}{% else %}{% url 'search_url' %}{% endif %}" method="GET">
- <input type="text" name="q" class="input-medium search-query" id="quick-search">
- <i class="input-icon icon-search"></i>
- <input type="submit" class="submit" value="">
- </form>
- {% endif %}
- {% endwith %}
- {% endblock %}
-
- {% include 'suit/menu.html' %}
-
- </div>
- {% endif %}
- {% endblock %}
-
- </div>
- </div>
- {% endblock %}
-
- {% if not is_popup %}
- <!-- Sticky footer push -->
- <div id="push"></div>
- {% endif %}
-
-</div>
-
-{% block footer %}
- {% if not is_popup %}
- <div id="footer" class="footer">
- <div class="content">
- <div class="tools">
- {% block footer_links %}
- <a href="http://djangosuit.com/support/" target="_blank" class="icon"><i class="icon-question-sign"></i>Support</a>
- <a href="http://djangosuit.com/pricing/" target="_blank" class="icon"><i class="icon-bookmark"></i>Licence</a>
- <a href="http://github.com/darklow/django-suit/issues" target="_blank" class="icon"><i class="icon-comment"></i>Report a bug</a>
- {% endblock %}
- </div>
-
- <div class="copyright">
- {% block copyright %}
- Copyright © 2013 DjangoSuit.com<br>Developed by <a href="http://djangosuit.com" target="_blank">DjangoSuit.com</a>
- {% endblock %}
- </div>
-
- <div class="branding">{% block footer_branding %}
- {% with 'ADMIN_NAME'|suit_conf as admin_name %}
- {{ admin_name }}
- {% if admin_name == 'Django Suit' %}
- v{{ 'VERSION'|suit_conf }}
- {% endif %}
- {% endwith %}
- {% endblock %}</div>
- </div>
- </div>
- {% endif %}
-{% endblock %}
-
- <script src="{% static 'suit/js/suit.js' %}"></script>
- {% block extrajs %}{% endblock %}
-
-</body>
-</html>
Pour effectuer votre paiement, veuillez cliquer sur l’URL suivante :
https://{{ site.domain }}{{ payment_url }}
+Après avoir cliqué sur le lien, vous arrivez sur la page de notre site mentionnant vos échéances et il ne vous reste plus qu’à cliquer sur le logo de votre carte bancaire pour accéder au formulaire de paiement correspondant.
+
Cordialement,
L’équipe du Pré-Barreau
{% payment_summary payment %}
- {% autoescape off %}
- {{ sherlock_info }}
- {% endautoescape %}
+<div class="container">
+ <div align="center">
+ Vous utilisez le formulaire sécurisé standard SSL, afin de poursuivre le paiement merci de sélectionner une carte ci-dessous <img src="/static/payment/logo/CLEF.gif" border="0" /> :
+ </div>
+ <form method="post" action="{{ sherlock_url }}">
+ <input type="hidden" name="Data" value="{{ sherlock_data }}" />
+ <input type="hidden" name="InterfaceVersion" value="HP_2.41" />
+ <input type="hidden" name="Seal" value="{{ sherlock_seal }}" />
+ <div align="center">
+ <input type="image" name="CB" src="/static/payment/logo/CB.gif" border="0" />
+
+ <input type="image" name="VISA" src="/static/payment/logo/VISA.gif" border="0" />
+
+ <input type="image" name="MASTERCARD" src="/static/payment/logo/MASTERCARD.gif" border="0" />
+ <br /
+ </div>
+ </form>
{% endblock %}
<div id="header">
<h2 style=" font-size: 15px">LE PRÉ-BARREAU <img src="https://{{ site.domain }}/static/teleforma/images/receip_logo.jpg" alt="" width="30px"> </h2>
- <p style="line-height: 15px; font-size: 10px; font-weight: bold;">
+ <p style="line-height: 15px; font-size: 9px; font-weight: bold;">
3, rue de Nesle<br />
75006 Paris<br />
Tel : 01.56.81.00.22<br />
<div id="content">
- <div class="address" style="margin-left: 320pt;">
- <p style="line-height: 15px; font-size: 10px; font-weight: bold;">
+ <div class="address" style="margin-left: 200pt;">
+ <p style="line-height: 15px; font-size: 9px; font-weight: bold;">
{{ student.user.first_name }}
{{ student.user.last_name }}
<br />
</div>
<div class="title" style= "margin-top: 30pt;
- margin-bottom: 15pt;
- font-size: 12px;
+ margin-bottom: 15pt;
+ font-size: 11px;
font-weight: bold;
text-transform: uppercase;
display: block;
</div>
- <table style="margin-top: 15pt; border-spacing : 0; border-collapse : collapse; table-layout: fixed;">
+ <table style="margin-top: 15pt; border-spacing : 0; border-collapse : collapse; table-layout: fixed">
<thead>
<tr style="border: 1px solid #000;">
- <th style="padding-top: 3pt; width: 350pt;">DESCRIPTION</th>
+ <th style="padding-top: 3pt; width: 300pt;">DESCRIPTION</th>
<th style="padding-top: 3pt;">PRIX UNITAIRE</th>
<th style="padding-top: 3pt;">QUANTITÉ</th>
<th style="padding-top: 3pt;">REMISE</th>
<tbody>
{% for item in receipt_items %}
<tr style="border-left: 1px solid #000; border-right: 1px solid #000;">
- <td style="padding: 3pt; width: 350pt; ">{{ item.label|safe }}</td>
+ <td style="padding: 3pt; width: 300pt; ">{{ item.label|safe }}</td>
<td style="padding: 3pt;">{{ item.unit_price|payment_format_amount }}</td>
<td style="padding: 3pt;">{{ item.amount|default_if_none:"" }}</td>
<td style="padding: 3pt;">{{ item.discount|payment_format_amount }}</td>
</tr>
{% endfor %}
<tr style="border: 1px solid #000; text-align: left;">
- <th style="padding: 3pt 3pt 0 3pt;text-align: left; width: 350pt;">Non assujetti à TVA</th>
+ <th style="padding: 3pt 3pt 0 3pt;text-align: left; width: 300pt;">Non assujetti à TVA</th>
<th style="padding: 3pt;"></th>
<th style="padding: 3pt;"></th>
<th style="padding: 3pt;"></th>
import logging
import os
import pprint
-import subprocess
+import hashlib
from django.conf import settings
from django.contrib.auth.decorators import login_required
log = logging.getLogger('payment')
-def call_scherlocks(what, data, merchant_id):
+def compute_sherlocks_seal(data, key):
"""
- Perform a Scherlock's call, with parameters in data
- what is either 'request' or 'response', the program to call
+ Compute the seal for Sherlock's
"""
- log.info('call_scherlocks %r %r' % (what, data))
- requestbin = os.path.join(
- settings.PAYMENT_SHERLOCKS_PATH, 'bin/static', what)
- params = dict(data)
- params['pathfile'] = os.path.join(settings.PAYMENT_SHERLOCKS_PATH,
- 'param/pathfile.' + merchant_id)
- params = ' '.join(['%s=%s' % (k, v) for k, v in params.items()])
- cmdline = requestbin + ' ' + params
+ data = data.encode('utf-8')
+ seal = hashlib.sha256(data + key.encode('utf-8')).hexdigest()
+ return seal
- status, out = subprocess.getstatusoutput(cmdline)
- if status:
- raise OSError("error calling %s" % cmdline)
- res = out.split('!')[1:-1]
- if int(res[0]):
- raise ValueError("Scherlock's returned %s" % res[1])
- return res[2:]
-
-
-def check_payment_info(data):
+def encode_sherlocks_data(params, key):
+ """
+ Encode data for Sherlock's
+ """
+ data = []
+ for k, v in params.items():
+ v = v.replace('|', '_').replace(',', '_').replace('=', '_')
+ data.append('%s=%s' % (k, v))
+ data = '|'.join(data)
+ seal = compute_sherlocks_seal(data, key)
+ return data, seal
+
+def check_payment_info(post_data):
"""
Check that the payment info are valid
"""
- response_code = data[8]
- cvv_response_code = data[14]
+ seal = post_data.get('Seal', None)
+ data = post_data.get('Data', None)
+ keys = [ settings.PAYMENT_PARAMETERS['_key'] ]
+
+ if not seal or not data:
+ log.warning('Missing seal or data')
+ raise SuspiciousOperation('Missing data')
+
+ for key in keys:
+ wanted_seal = compute_sherlocks_seal(data, key)
+ if seal == wanted_seal:
+ break
+ else:
+ log.warning('Invalid seal')
+ raise SuspiciousOperation('Invalid seal')
- log.info('check_payment_info %s %s' % (response_code, cvv_response_code))
+ words = data.split('|')
+ values = {}
+ for word in words:
+ if "=" in word:
+ key, value = word.split('=', 1)
+ values[key] = value
- return response_code == '00'
+ order_id = values.get('orderId', None)
+ code = values.get('responseCode', None)
+ if not order_id or not code:
+ log.warning('Missing order_id or code')
+ raise SuspiciousOperation('Missing value in data')
+
+ res = { 'order_id': order_id,
+ 'valid': code == '00' }
+
+ log.debug('check_payment_info %s %s' % (order_id, code))
+
+ return res
def process_payment(request, payment):
Process a payment to Sherlocks
"""
params = dict(settings.PAYMENT_PARAMETERS)
- if isinstance(params['merchant_id'], dict):
- period = payment.student.period
- period_short_name = period.name.split()[0]
- params['merchant_id'] = params['merchant_id'][period_short_name]
- merchant_id = params['merchant_id']
+ key = params.pop('_key')
+ merchant_id = params['merchantId']
params['amount'] = str(int(payment.value*100))
- params['order_id'] = str(payment.pk)
+ params['orderId'] = str(payment.pk)
+ if settings.SHERLOKS_USE_TRANSACTION_ID:
+ params['s10TransactionReference.s10TransactionId'] = '%06d' % payment.pk
+ else:
+ params['transactionReference'] = str(payment.pk)
current_site = get_current_site(request)
root = 'https://%s' % (current_site.domain)
kwargs = {'merchant_id': merchant_id}
- params['normal_return_url'] = root + reverse('teleforma-bank-success',
- kwargs=kwargs)
- params['cancel_return_url'] = root + reverse('teleforma-bank-cancel',
- kwargs=kwargs)
- params['automatic_response_url'] = root + reverse('teleforma-bank-auto',
- kwargs=kwargs)
- pprint.pprint(params)
- res = call_scherlocks('request', params, merchant_id=merchant_id)
- return res[0]
+ params['normalReturnUrl'] = root + reverse('teleforma-bank-success',
+ kwargs=kwargs)
+ params['automaticResponseURL'] = root + reverse('teleforma-bank-auto',
+ kwargs=kwargs)
+ data, seal = encode_sherlocks_data(params, key)
+ return data, settings.SHERLOKS_URL, seal
class PaymentStartView(DetailView):
if payment.student.user_id != self.request.user.pk and not self.request.user.is_superuser:
raise PermissionDenied
context['payment'] = payment
- context['sherlock_info'] = process_payment(self.request, payment)
+ data, url, seal = process_payment(self.request, payment)
+ context['sherlock_url'] = url
+ context['sherlock_data'] = data
+ context['sherlock_seal'] = seal
return context
@method_decorator(login_required)
"""
Bank automatic callback
"""
- res = call_scherlocks('response', {'message': request.POST['DATA']},
- merchant_id=merchant_id)
- order_id = res[24]
+ log.info("bank_auto %r" % request.POST)
+ info = check_payment_info(request.POST)
+ order_id = info['order_id']
payment = Payment.objects.get(pk=order_id)
- if check_payment_info(res) and payment.type == 'online' and not payment.online_paid:
+ if info['valid'] and payment.type == 'online' and not payment.online_paid:
payment.online_paid = True
payment.date_paid = datetime.datetime.now()
- if payment.student.restricted:
- payment.student.restricted = False
- student = payment.student
- # send mail
- data = {
- 'mfrom': settings.DEFAULT_FROM_EMAIL,
- 'mto': payment.student.user.email,
- 'student': payment.student
- }
- message = render_to_string(
- 'teleforma/messages/email_account_activated.txt', data)
- send_mail("Inscription à la formation Pré-Barreau", message, data['mfrom'], [data['mto']],
- fail_silently=False)
- student.save()
-
payment.save()
log.info('bank_auto validating order_id %s' % (order_id))
tmpl_name = 'payment_ok'
"""
Bank success callback
"""
- log.info("bank_success %r" % request.POST)
- res = call_scherlocks('response', {'message': request.POST['DATA']},
- merchant_id=merchant_id)
- if check_payment_info(res):
- order_id = res[24]
+ log.info("bank_auto %r" % request.POST)
+ info = check_payment_info(request.POST)
+ order_id = info['order_id']
+ if info['valid']:
payment = Payment.objects.get(pk=order_id)
if payment.type == 'online' and payment.online_paid:
return render(request, 'payment/payment_validate.html',