add cmcic library
authorNicolas Chauvat <nicolas.chauvat@logilab.fr>
Mon, 21 Jun 2010 20:27:00 +0200
changeset 1 bbea5dfcadcb
parent 0 7269796db39f
child 2 80232b459741
add cmcic library
cmcic.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmcic.py	Mon Jun 21 20:27:00 2010 +0200
@@ -0,0 +1,273 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+=======================================
+Python module to handle CM-CIC p@iement
+=======================================
+
+Config file
+============
+
+Config file must look like:
+
+[cmcic_tpe]
+number: 0123456
+key: 0123456789012345678901234567890123456789
+version: 3.0
+company: acme
+
+Submit payment
+==============
+
+Example of code that generates the html page with the form that links to the
+payment server.
+
+.. sourcecode:: python
+
+    import cmcic, ConfigParser
+
+    # initialize terminal
+    cfg = ConfigParser.RawConfigParser()
+    cfg.read('mytpe')
+    tpe = cmcic.PaymentTerminal(cfg)
+
+    # build transaction
+    transac = cmcic.Transaction()
+    transac.reference = "ref" + datetime.datetime.now().strftime("%H%M%S");
+    transac.amount = "1.01"
+    transac.currency = "EUR"
+    transac.description = "Some description"
+    transac.date = datetime.datetime.now().strftime("%d/%m/%Y:%H:%M:%S")
+    transac.lang = "FR"
+    transac.email = "test@test.zz"
+    errors = transac.has_errors()
+    if errors:
+        print errors
+        sys.exit(1)
+
+    # generate html
+    print '''
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
+    <head>
+    <title>Test serveur de paiement CMCIC</title>
+    </head>
+
+    <body>
+    <h1>Test serveur de paiement CMCIC</h1>
+
+    <p>Identification du terminal <pre>%s</pre></p>
+    <p>Sceau MAC <pre>%s</pre></p>
+    %s
+    </body>
+    </html>
+    ''' % (tpe.id_str(),
+           tpe.mk_transaction_msg(transac),
+           cmcic.html_form(tpe, transac))
+
+Acknowledge payment
+===================
+
+Example of code that handles the response from the payment server and
+acknowledges that response upon reception.
+
+.. sourcecode:: python
+
+    import cmcic, cgi, ConfigParser
+
+    # initialize terminal
+    cfg = ConfigParser.RawConfigParser()
+    cfg.read('mytpe')
+    tpe = cmcic.PaymentTerminal(cfg)
+
+    # handle response
+    cert = cmcic.Certification(cgi.FieldStorage())
+    if tpe.is_valid_cert(cert):
+        if cert.return_code == "Annulation":
+            # Payment has been refused
+            # The payment may be accepted later
+            # put your code here (email sending / Database update)
+            pass
+
+        elif cert.return_code in ("payetest","paiement"):
+            # Payment has been accepeted on the productive server
+            # put your code here (email sending / Database update)
+            pass
+
+        #*** ONLY FOR MULTIPART PAYMENT ***#
+        elif cert.return_code.startswith("paiement_pf"):
+            # Payment has been accepted on the productive server for the part #N
+            # return code is like paiement_pf[#N]
+            # put your code here (email sending / Database update)
+            # You have the amount of the payment part in Certification['montantech']
+            pass
+
+        elif cert.return_code.startswith("Annulation_pf"):
+            # Payment has been refused on the productive server for the part #N
+            # return code is like Annulation_pf[#N]
+            # put your code here (email sending / Database update)
+            # You have the amount of the payment part in Certification['montantech']
+            pass
+
+        sResult = "0"
+    else :
+        # your code if the HMAC doesn't match
+        sResult = "1\n" + tpe.mk_certification_msg(cert)
+
+    #-----------------------------------------------------------------------------
+    # Send receipt to CMCIC server
+    #-----------------------------------------------------------------------------
+    print "Pragma: no-cache\nContent-type: text/plain\n\nversion=2\ncdr=" + sResult
+
+"""
+__docformat__ = 'restructuredtext'
+
+import sys, hmac, sha
+
+class Transaction(object):
+    """
+    reference  : unique, alphaNum (A-Z a-z 0-9), 12 characters max
+    amount     : format  "xxxxx.yy" (no spaces)
+    currency   : ISO 4217 compliant
+    description: session context for the return on the merchant website
+    date       : format dd/mm/yyyy:hh:mm:ss
+    email      : buyer's email
+    payments   : amount must be paid in at most 4 payments [(date1, amount1), ...]
+    """
+
+    def __init__(self):
+        self.reference = None
+        self.amount = None
+        self.currency = None
+        self.description = None
+        self.date = None
+        self.email = None
+        self.payments = []
+        self.options = None
+
+    def has_errors(self):
+        errors = []
+        if not self.reference:
+            errors.append('reference is missing')
+        if not self.amount:
+            errors.append('amount is missing')
+        if not self.currency:
+            errors.append('currency is missing')
+        if not self.date:
+            errors.append('date is missing')
+        if not self.email:
+            errors.append('email is missing')
+        if self.payments and sum(item[1] for item in payments) != self.amount:
+            errors.append('sum of payments != amount')
+        return errors
+
+CERT_MAP = [('MAC','mac'), ('date','date'), ('montant','amount'),
+            ('reference','reference'), ('texte-libre','description'),
+            ('code-retour','return_code'), ('cvx','cvx'), ('vld','vld'),
+            ('brand','brand'), ('status3ds', 'status3ds'),
+            ('numauto','numauto'), ('motifrefus','motifrefus'),
+            ('originecb','originecb'), ('bincb','bincb'), ('hpancb', 'hpancb'),
+            ('ipclient', 'ipclient'), ('originetr', 'originetr'),
+            ('veres', 'veres'), ('pares','pares'), ('montantech', 'montantech'),
+            ]
+
+class Certification(object):
+
+    def __init__(self, params):
+        for src, dst in CERT_MAP:
+            if params.has_key(src):
+                setattr(self, dst, params[src].value)
+            else:
+                setattr(self, dst, None)
+
+class PaymentTerminal(object):
+
+    URL_SERVEUR = "https://paiement.creditmutuel.fr/paiement.cgi"
+    URL_SERVEUR_TEST = "https://paiement.creditmutuel.fr/test/paiement.cgi"
+
+    def __init__(self, cfg, test=True) :
+        self.version = cfg.get('cmcic_tpe', 'version')
+        self._cle = cfg.get('cmcic_tpe', 'key')
+        self.numero = cfg.get('cmcic_tpe', 'number')
+        self.code_societe = cfg.get('cmcic_tpe', 'company')
+        self.url_paiement = test and self.URL_SERVEUR_TEST or self.URL_SERVEUR
+        self.url_ok = 'http://www.google.fr/'
+        self.url_ko = 'http://www.google.de/'
+        self._usablekey = self.getUsableKey()
+
+    def getUsableKey(self):
+        hexStrKey  = self._cle[0:38]
+        hexFinal   = self._cle[38:40] + "00"
+        cca0 = ord(hexFinal[0:1])
+        if cca0 > 70 and cca0 < 97:
+            hexStrKey += chr(cca0-23) + hexFinal[1:2]
+        elif hexFinal[1:2] == "M":
+            hexStrKey += hexFinal[0:1] + "0"
+        else:
+            hexStrKey += hexFinal[0:2]
+        import encodings.hex_codec
+        c = encodings.hex_codec.Codec()
+        hexStrKey = c.decode(hexStrKey)[0]
+        return hexStrKey
+
+    def compute_hmac(self, data):
+        hash = hmac.HMAC(self._usablekey, None, sha)
+        hash.update(data)
+        return hash.hexdigest()
+
+    def is_valid_hmac(self, data, mac):
+        return self.compute_hmac(data) == mac.lower()
+
+    def id_str(self):
+        msg = "CtlHmac%s%s" % (self.version, self.numero)
+        return "V1.04.sha1.py--[%s]-%s" % (msg, self.compute_hmac(msg))
+
+    def mk_transaction_msg(self, transac):
+        items = [self.numero, transac.date, transac.amount+transac.currency,
+                 transac.reference, transac.description, self.version,
+                 transac.lang, self.code_societe, transac.email]
+        if len(transac.payments):
+            items.append(len(transac.payments))
+        else:
+            items.append('')
+        for date, amount in transac.payments:
+            items.append(date)
+            items.append(amount+transac.currency)
+        for i in range(len(transac.payments),4):
+            items.append('')
+            items.append('')
+        items.append(transac.options or '')
+        items = [str(item) for item in items]
+        return '*'.join(items)
+
+    def mk_certification_msg(self, cert):
+        items = [self.numero, cert.date, cert.amount, cert.reference, cert.description,
+                 self.version, cert.return_code, cert.cvx, cert.vld, cert.brand, cert.status3ds,
+                 cert.numauto, cert.motifrefus, cert.originecb, cert.bincd, cert.hpancb,
+                 cert.ipclient, cert.originetr, cert.veres, cert.pares, '']
+        return '*'.join(items)
+
+    def is_valid_cert(self, cert):
+        return self.is_valid_hmac(self.mk_certification_msg(cert), cert.mac)
+
+def html_form(tpe, transac):
+    form = ['<form action="%s" method="post" id="PaymentRequest">' % tpe.url_paiement]
+    fields = [("version"         ,tpe.version),
+              ("TPE"             ,tpe.numero),
+              ("date"            ,transac.date),
+              ("montant"         ,transac.amount + transac.currency),
+              ("reference"       ,transac.reference),
+              ("MAC"             ,tpe.compute_hmac(tpe.mk_transaction_msg(transac))),
+              ("url_retour"      ,tpe.url_ko),
+              ("url_retour_ok"   ,tpe.url_ok),
+              ("url_retour_err"  ,tpe.url_ko),
+              ("lgue"            ,transac.lang),
+              ("societe"         ,tpe.code_societe),
+              ("texte-libre"     ,transac.description),
+              ("mail"            ,transac.email),
+              ]
+    for name, value in fields:
+	form.append('<input type="hidden" name="%s" id="%s" value="%s" />' % (name, name, value))
+    form.append('<input type="submit" name="bouton" id="bouton" value="Connexion / Connection" />')
+    form.append('</form>')
+    return ''.join(form)