author Nicolas Chauvat <>
Sun, 28 Nov 2010 21:45:17 +0100
changeset 20 a6b01c4dfc9d
parent 15 c6477e1537bf
child 24 87b1c861f1ca
permissions -rw-r--r--
[views] fix cmcic_info

# -*- coding: utf-8 -*-
Python module to handle CM-CIC p@iement

Config file

Config file must look like:

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

    # initialize terminal
    tpe = cmcic.get_tpe('mytpe')

    # build payment request
    payreq = cmcic.PaymentRequest()
    payreq.reference = "ref" +"%H%M%S");
    payreq.amount = "1.01"
    payreq.currency = "EUR"
    payreq.description = "Some description" ="%d/%m/%Y:%H:%M:%S")
    payreq.lang = "FR" = "test@test.zz"
    payreq.url_root = ''
    payreq.url_ok = ''
    payreq.url_err = ''

    # generate html
    print '''
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "">
    <html xmlns="" xml:lang="fr" lang="fr">
    <title>Test serveur de paiement CMCIC</title>

    <h1>Test serveur de paiement CMCIC</h1>

    <p>Identification du terminal <pre>%s</pre></p>
    <p>Sceau MAC <pre>%s</pre></p>
    ''' % (tpe.id_str(),
           cmcic.html_form(tpe, payreq))

Acknowledge payment

Example of code that handles the response from the payment server and
acknowledges that response upon reception.

.. sourcecode:: python

    import cmcic

    # initialize terminal
    tpe = cmcic.get_tpe('mytpe')

    # handle response
    payrep = tpe.read_paymentresponse(cgi.FieldStorage())
    msg, mac = tpe.paymentresponse_msg(payrep)
    if tpe.is_valid_msg(msg, mac):
        if payrep.return_code == "Annulation":
            # Payment has been refused
            # The payment may be accepted later

        elif payrep.return_code in ("payetest","paiement"):
            # Payment has been accepeted on the productive server

        elif payrep.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']

        elif payrep.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']

        sResult = "0"
    else :
        # your code if the HMAC doesn't match
        sResult = "1\n" + mac

    # Send receipt to CMCIC server
    print "Pragma: no-cache\nContent-type: text/plain\n\nversion=2\ncdr=" + sResult

__docformat__ = 'restructuredtext'

import hmac, sha, os.path, ConfigParser

def dict_translate(msg, map):
    result = {}
    for key, value in msg.items():
        key = map.get(key, key)
        if key is not None:
            result[key] = value
    return result

class NamedTuple(object):
    __slots__ = ()

    def __init__(self, **kwargs):
        for attr in self.__slots__:
            setattr(self, attr, kwargs.pop(attr, None))
        if kwargs:
            raise NameError('No attributes named %s' % repr(kwargs))

    def as_dict(self):
        result = {}
        for attr in self.__slots__:
            result[attr] = getattr(self, attr)
        return result

class PaymentRequest(NamedTuple):
    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), ...]
    options    : ...
    __slots__ = ('mac reference amount currency description date email payments '
                 'options url_root url_ok url_err lang').split()

class PaymentResponse(NamedTuple):
    __slots__ = ('mac reference amount date description return_code cvx vld '
                 'brand status3ds numauto motifrefus originecb bincb hpancb '
                 'ipclient originetr veres pares montantech').split()

# same order as in CM-CIC_paiement_documentation_technique_v3_0.pdf
    ('MAC','mac'), ('TPE', None), ('montant','amount'), ('texte-libre','description'),

    ('version', None), ('TPE', None), ('montant', 'amount'),
    ('texte-libre', 'description'), ('mail', 'email'),
    ('lgue','lang'), ('societe', None), ('url_retour', 'url_root'),
    ('url_retour_ok', 'url_ok'), ('url_retour_err', 'url_err'),
    ('MAC','mac'), ('bouton', None),

class PaymentProtocol(object):

    def __init__(self, cfg) :
        self.version = cfg.get('cmcic_tpe', 'version')
        self.tpe_key = self.decode_key(cfg.get('cmcic_tpe', 'key'))
        self.tpe_number = cfg.get('cmcic_tpe', 'number')
        self.tpe_company = cfg.get('cmcic_tpe', 'company')
        self.server_url = cfg.get('cmcic_tpe', 'server_url')
        self.return_url = cfg.get('cmcic_tpe', 'return_url')

    def decode_key(self, key):
        hexStrKey = key[0:38]
        hexFinal = key[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"
            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.tpe_key, None, sha)
        return hash.hexdigest()

    def id_str(self):
        msg = "CtlHmac%s%s" % (self.version, self.tpe_number)
        return "[%s]-%s" % (msg, self.compute_hmac(msg))

    def read_paymentrequest(self, params):
        params = dict_translate(params, REQUEST_TRANSLATION)
        req = PaymentRequest(**params)
        return req

    def read_paymentresponse(self, params):
        params = dict_translate(params, RESPONSE_TRANSLATION)
        req = PaymentResponse(**params)
        return req

    def paymentrequest_msg(self, req):
        items = [self.tpe_number,, req.amount+req.currency,
                 req.reference, req.description, self.version,
                 req.lang, self.tpe_company,]
        payments = req.payments or []
        if len(payments):
        for date, amount in payments:
        for i in range(len(payments),4):
        items.append(req.options or '')
        items = [str(item) for item in items]
        msg = '*'.join(items)
        hmac = self.compute_hmac(msg)
        return msg, hmac

    def paymentresponse_msg(self, rep):
        items = []
        for item in [self.tpe_number,, rep.amount, rep.reference, rep.description,
                     rep.return_code, rep.cvx, rep.vld, rep.brand, rep.status3ds,
                     rep.numauto, rep.motifrefus, rep.originecb, rep.bincb, rep.hpancb,
                     rep.ipclient, rep.originetr, rep.veres, rep.pares, '']:
            if item is None:
        msg = '*'.join(items)
        hmac = self.compute_hmac(msg)
        return msg, hmac

    def is_valid_msg(self, msg, mac):
        return self.compute_hmac(msg) == mac.lower()

# by default submit = u'<input type="submit" name="bouton" id="bouton" value="%s" />' % label

def html_form(tpe, req, submit):
    form = [u'<form action="%s" method="post" id="PaymentRequest">' % tpe.server_url]
    msg, mac = tpe.paymentrequest_msg(req)
    fields = [("version"         ,tpe.version),
              ("TPE"             ,tpe.tpe_number),
              ("date"            ,,
              ("montant"         ,req.amount + req.currency),
              ("reference"       ,req.reference),
              ("MAC"             ,mac),
              ("url_retour"      ,req.url_root),
              ("url_retour_ok"   ,req.url_ok),
              ("url_retour_err"  ,req.url_err),
              ("lgue"            ,req.lang),
              ("societe"         ,tpe.tpe_company),
              ("texte-libre"     ,req.description),
              ("mail"            ,,
    for name, value in fields:
	form.append(u'<input type="hidden" name="%s" id="%s" value="%s" />' % (name, name, value))
    return u''.join(form)

def get_tpe(cfgpath):
    cfg = ConfigParser.RawConfigParser()
    return PaymentProtocol(cfg)