PayzenService.py 9.6 KB
Newer Older
1 2 3 4
import zope
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.XMLObject import XMLObject
Łukasz Nowak's avatar
Łukasz Nowak committed
5 6
from Products.ERP5Type.Document import newTempDocument
import hashlib
7
from zLOG import LOG, WARNING
8
import datetime
9

Łukasz Nowak's avatar
Łukasz Nowak committed
10 11 12 13 14 15 16
try:
  import suds
except ImportError:
  class PayzenSOAP:
    pass
else:
  class PayzenSOAP:
Łukasz Nowak's avatar
Łukasz Nowak committed
17
    """SOAP communication
18

Łukasz Nowak's avatar
Łukasz Nowak committed
19 20
    Methods are returning list of:
      * parsed response
21
      * signature check (True or False)
Łukasz Nowak's avatar
Łukasz Nowak committed
22 23
      * sent XML
      * received XML
24

Łukasz Nowak's avatar
Łukasz Nowak committed
25 26
    SOAP protocol is assumed as untrusted and dangerous, users of those methods
    are encouraged to log such messages for future debugging."""
Łukasz Nowak's avatar
Łukasz Nowak committed
27
    def _check_transactionInfoSignature(self, data):
28 29 30
      """Checks transactionInfo signature
      Can raise.
      """
31 32 33 34 35 36 37 38 39 40
      received_sorted_keys = ['errorCode', 'extendedErrorCode',
        'transactionStatus', 'shopId', 'paymentMethod', 'contractNumber',
        'orderId', 'orderInfo', 'orderInfo2', 'orderInfo3', 'transmissionDate',
        'transactionId', 'sequenceNb', 'amount', 'initialAmount', 'devise',
        'cvAmount', 'cvDevise', 'presentationDate', 'type', 'multiplePaiement',
        'ctxMode', 'cardNumber', 'cardNetwork', 'cardType', 'cardCountry',
        'cardExpirationDate', 'customerId', 'customerTitle', 'customerName',
        'customerPhone', 'customerMail', 'customerAddress', 'customerZipCode',
        'customerCity', 'customerCountry', 'customerLanguage', 'customerIP',
        'transactionCondition', 'vadsEnrolled', 'vadsStatus', 'vadsECI',
Łukasz Nowak's avatar
Łukasz Nowak committed
41
        'vadsXID', 'vadsCAVVAlgorithm', 'vadsCAVV', 'vadsSignatureValid',
42 43 44 45
        'directoryServer', 'authMode', 'markAmount', 'markDevise', 'markDate',
        'markNb', 'markResult', 'markCVV2_CVC2', 'authAmount', 'authDevise',
        'authDate', 'authNb', 'authResult', 'authCVV2_CVC2', 'warrantlyResult',
        'captureDate', 'captureNumber', 'rapprochementStatut', 'refoundAmount',
46
        'refundDevise', 'litige', 'timestamp']
Łukasz Nowak's avatar
Łukasz Nowak committed
47 48

      signature = self._getSignature(data, received_sorted_keys)
49 50
      return signature == data.signature

Łukasz Nowak's avatar
Łukasz Nowak committed
51
    def soap_getInfo(self, transmissionDate, transactionId):
Łukasz Nowak's avatar
Łukasz Nowak committed
52
      """Returns getInfo as dict, booelan, string, string
53

Łukasz Nowak's avatar
Łukasz Nowak committed
54
      transmissionDate is "raw" date in format YYYYMMDD, without any marks
Łukasz Nowak's avatar
Łukasz Nowak committed
55 56 57 58
      transactionId is id of transaction for this date

      As soon as communication happeneded does not raise.
      """
Łukasz Nowak's avatar
Łukasz Nowak committed
59
      client = suds.client.Client(self.wsdl_link.getUrlString())
Łukasz Nowak's avatar
Łukasz Nowak committed
60 61
      sorted_keys = ['shopId', 'transmissionDate', 'transactionId',
        'sequenceNb', 'ctxMode']
Łukasz Nowak's avatar
Łukasz Nowak committed
62 63 64 65 66
      kw = dict(
        transactionId=transactionId,
        ctxMode=self.getPayzenVadsCtxMode(),
        shopId=self.getServiceUsername(),
        sequenceNb=1,
Łukasz Nowak's avatar
Łukasz Nowak committed
67
        transmissionDate=transmissionDate,
Łukasz Nowak's avatar
Łukasz Nowak committed
68
      )
Łukasz Nowak's avatar
Łukasz Nowak committed
69
      kw['wsSignature'] = self._getSignature(kw, sorted_keys)
Łukasz Nowak's avatar
Łukasz Nowak committed
70

Łukasz Nowak's avatar
Łukasz Nowak committed
71
      data = client.service.getInfo(**kw)
Łukasz Nowak's avatar
Łukasz Nowak committed
72 73
      # Note: Code shall not raise since now, as communication begin and caller
      #       will have to log sent/received messages.
74
      try:
Łukasz Nowak's avatar
Łukasz Nowak committed
75 76 77 78 79 80 81 82 83 84 85 86
        data_kw = dict(data)
        for k in data_kw.keys():
          v = data_kw[k]
          if not isinstance(v, str):
            data_kw[k] = str(v)
      except Exception:
        data_kw = {}
        signature = False
        LOG('PayzenService', WARNING,
          'Issue during processing data_kw:', error=True)
      else:
        try:
Łukasz Nowak's avatar
Łukasz Nowak committed
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
          signature = self._check_transactionInfoSignature(data)
        except Exception:
          LOG('PayzenService', WARNING, 'Issue during signature calculation:',
            error=True)
          signature = False

      try:
        last_sent = str(client.last_sent())
      except Exception:
        LOG('PayzenService', WARNING,
          'Issue during converting last_sent to string:', error=True)
        signature = False

      try:
        last_received = str(client.last_received())
      except Exception:
        LOG('PayzenService', WARNING,
          'Issue during converting last_received to string:', error=True)
        signature = False

      return [data_kw, signature, last_sent, last_received]

    def soap_duplicate(self, transmissionDate, transactionId, presentationDate,
      newTransactionId, amount, devise, orderId='', orderInfo='', orderInfo2='',
      orderInfo3='', validationMode=0, comment=''):
      # prepare with passed parameters
      kw = dict(transmissionDate=transmissionDate, transactionId=transactionId,
        presentationDate=presentationDate, newTransactionId=newTransactionId,
        amount=amount, devise=devise, orderId=orderId, orderInfo=orderInfo,
        orderInfo2=orderInfo2, orderInfo3=orderInfo3,
        validationMode=validationMode, comment=comment)

      signature_sorted_key_list= ['shopId', 'transmissionDate', 'transactionId',
        'sequenceNb', 'ctxMode', 'orderId', 'orderInfo', 'orderInfo2',
        'orderInfo3', 'amount', 'devise', 'newTransactionId',
        'presentationDate', 'validationMode', 'comment']
      kw.update(
        ctxMode=self.getPayzenVadsCtxMode(),
        shopId=self.getServiceUsername(),
        sequenceNb=1,
      )
      kw['wsSignature'] = self._getSignature(kw, signature_sorted_key_list)
      # Note: Code shall not raise since now, as communication begin and caller
      #       will have to log sent/received messages.
      client = suds.client.Client(self.wsdl_link.getUrlString())
      data = client.service.duplicate(**kw)
      # Note: Code shall not raise since now, as communication begin and caller
      #       will have to log sent/received messages.
      try:
        data_kw = dict(data)
        for k in data_kw.keys():
          v = data_kw[k]
          if not isinstance(v, str):
            data_kw[k] = str(v)
      except Exception:
        data_kw = {}
        signature = False
        LOG('PayzenService', WARNING,
          'Issue during processing data_kw:', error=True)
      else:
        try:
          signature = self._check_transactionInfoSignature(data)
Łukasz Nowak's avatar
Łukasz Nowak committed
149 150 151 152 153 154 155
        except Exception:
          LOG('PayzenService', WARNING, 'Issue during signature calculation:',
            error=True)
          signature = False

      try:
        last_sent = str(client.last_sent())
156
      except Exception:
Łukasz Nowak's avatar
Łukasz Nowak committed
157 158
        LOG('PayzenService', WARNING,
          'Issue during converting last_sent to string:', error=True)
159
        signature = False
Łukasz Nowak's avatar
Łukasz Nowak committed
160 161 162 163 164 165 166 167 168

      try:
        last_received = str(client.last_received())
      except Exception:
        LOG('PayzenService', WARNING,
          'Issue during converting last_received to string:', error=True)
        signature = False

      return [data_kw, signature, last_sent, last_received]
Łukasz Nowak's avatar
Łukasz Nowak committed
169 170

class PayzenService(XMLObject, PayzenSOAP):
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
  meta_type = 'Payzen Service'
  portal_type = 'Payzen Service'

  zope.interface.implements(interfaces.IPaymentService)

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.XMLObject
                    , PropertySheet.Reference
                    )
  def initialize(self, REQUEST=None, **kw):
    """See Payment Service Interface Documentation"""
    pass

Łukasz Nowak's avatar
Łukasz Nowak committed
189
  def _getSignature(self, ob, sorted_key_list):
Łukasz Nowak's avatar
Łukasz Nowak committed
190 191 192 193 194 195 196 197 198 199 200 201
    """Calculates signature from ob

    ob can be dict or getattr capable object

    in case if ob is a dict all .strftime callable values
    are converted to datetime soapish format
    """
    if isinstance(ob, dict):
      isdict = True
    else:
      isdict = False

Łukasz Nowak's avatar
Łukasz Nowak committed
202
    signature = ''
Łukasz Nowak's avatar
Łukasz Nowak committed
203 204 205 206 207 208 209 210
    for k in sorted_key_list:
      if isdict:
        v = ob[k]
      else:
        v = getattr(ob, k, None)
      if v is None:
        # empty or not transmitted -- add as empty
        v = ''
Łukasz Nowak's avatar
Łukasz Nowak committed
211
      elif isinstance(v, datetime.datetime):
Łukasz Nowak's avatar
Łukasz Nowak committed
212
        # for sure date
Łukasz Nowak's avatar
Łukasz Nowak committed
213
        v = v.strftime('%Y%m%d')
Łukasz Nowak's avatar
Łukasz Nowak committed
214 215 216
      else:
        # anything else cast to string
        v = str(v)
Łukasz Nowak's avatar
Łukasz Nowak committed
217 218 219 220 221
      signature += v + '+'
    signature += self.getServicePassword()
    return hashlib.sha1(signature).hexdigest()

  def _getFieldList(self, payzen_dict):
Łukasz Nowak's avatar
Łukasz Nowak committed
222 223 224 225 226 227 228 229 230
    payzen_dict.update(
      vads_action_mode=self.getPayzenVadsActionMode(),
      vads_ctx_mode=self.getPayzenVadsCtxMode(),
      vads_contrib='ERP5',
      vads_page_action=self.getPayzenVadsPageAction(),
      vads_payment_config='SINGLE',
      vads_site_id=self.getServiceUsername(),
      vads_version=self.getPayzenVadsVersion()
    )
Łukasz Nowak's avatar
Łukasz Nowak committed
231
    # fetch all prepared vads_ values and remove them from dict
Łukasz Nowak's avatar
Łukasz Nowak committed
232
    signature = self._getSignature(payzen_dict, sorted(payzen_dict.keys()))
Łukasz Nowak's avatar
Łukasz Nowak committed
233 234
    payzen_dict['signature'] = signature
    field_list = []
Łukasz Nowak's avatar
Łukasz Nowak committed
235 236 237 238 239 240 241 242
    for k,v in payzen_dict.iteritems():
      field_list.append((k, v))
    return field_list

  def navigate(self, page_template, payzen_dict, REQUEST=None, **kw):
    """Returns configured template used to do the payment"""
    self.Base_checkConsistency()
    temp_document = newTempDocument(self, 'id')
Łukasz Nowak's avatar
Łukasz Nowak committed
243 244 245
    temp_document.edit(
      link_url_string=self.getLinkUrlString(),
      title='title',
Łukasz Nowak's avatar
Łukasz Nowak committed
246
      field_list=self._getFieldList(payzen_dict),
Łukasz Nowak's avatar
Łukasz Nowak committed
247 248 249 250
      # append the rest of transmitted parameters page template
      **kw
    )
    return getattr(temp_document, page_template)()
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265

  def notifySuccess(self, REQUEST=None, **kw):
    """See Payment Service Interface Documentation"""
    raise NotImplementedError
    return self._getTypeBasedMethod("acceptPayment")(**kw)

  def notifyFail(self, REQUEST=None, **kw):
    """See Payment Service Interface Documentation"""
    raise NotImplementedError
    return self._getTypeBasedMethod("failInPayment")(**kw)

  def notifyCancel(self, REQUEST=None, **kw):
    """See Payment Service Interface Documentation"""
    raise NotImplementedError
    return self._getTypeBasedMethod("abortPayment")(**kw)