Commit d6dcd5a4 authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

erp5_payzen_secure_payment: Move Payzen to REST API

* Soap Duplicate and Soap Cancel functions have been discontinued due to lack of usage
* OrderId is now necessary in order to retrieve Transactions
parent ed59cfeb
......@@ -4,9 +4,11 @@ from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type.XMLObject import XMLObject
import hashlib
from zLOG import LOG, WARNING
import base64
import datetime
import os
import time
import requests
from Products.DCWorkflow.DCWorkflow import ValidationFailed
present = False
......@@ -16,13 +18,7 @@ if 'TZ' in os.environ:
tz = os.environ['TZ']
os.environ['TZ'] = 'UTC'
time.tzset()
try:
import suds
except ImportError:
class PayzenSOAP:
pass
else:
def setUTCTimeZone(fn):
def setUTCTimeZone(fn):
def wrapped(*args, **kwargs):
present = False
tz = None
......@@ -41,43 +37,42 @@ else:
time.tzset()
return wrapped
class PayzenSOAP:
"""SOAP communication
if present:
os.environ['TZ'] = tz
else:
del(os.environ['TZ'])
time.tzset()
class PayzenREST:
"""REST communication
Methods are returning list of:
* parsed response
* signature check (True or False)
* sent XML
* received XML
SOAP protocol is assumed as untrusted and dangerous, users of those methods
are encouraged to log such messages for future debugging."""
def _check_transactionInfoSignature(self, data):
"""Checks transactionInfo signature
Can raise.
* sent Data
* received Data
"""
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',
'vadsXID', 'vadsCAVVAlgorithm', 'vadsCAVV', 'vadsSignatureValid',
'directoryServer', 'authMode', 'markAmount', 'markDevise', 'markDate',
'markNb', 'markResult', 'markCVV2_CVC2', 'authAmount', 'authDevise',
'authDate', 'authNb', 'authResult', 'authCVV2_CVC2', 'warrantlyResult',
'captureDate', 'captureNumber', 'rapprochementStatut', 'refoundAmount',
'refundDevise', 'litige', 'timestamp']
signature = self._getSignature(data, received_sorted_keys)
return signature == data.signature
def callPayzenApi(self, URL, payzen_dict):
base64string = base64.encodestring(
'%s:%s' % (
self.getServiceUsername(),
self.getServiceApiKey())).replace('\n', '')
header = {"Authorization": "Basic %s" % base64string}
LOG('callPayzenApi', WARNING,
"data = %s URL = %s" % (str(payzen_dict), URL), error=False)
# send data
result = requests.post(URL, data=payzen_dict, headers=header)
try:
data = result.json()
except Exception:
data = {}
LOG('PayzenService', WARNING,
'Issue during processing data_kw:', error=True)
return data, result.text
@setUTCTimeZone
def soap_getInfo(self, transmissionDate, transactionId):
def rest_getInfo(self, transmissionDate, transactionId):
"""Returns getInfo as dict, booelan, string, string
transmissionDate is "raw" date in format YYYYMMDD, without any marks
......@@ -85,181 +80,17 @@ else:
As soon as communication happeneded does not raise.
"""
client = suds.client.Client(self.wsdl_link.getUrlString())
sorted_keys = ['shopId', 'transmissionDate', 'transactionId',
'sequenceNb', 'ctxMode']
URL = "https://api.payzen.eu/api-payment/V4/Order/Get"
kw = dict(
transactionId=transactionId,
ctxMode=self.getPayzenVadsCtxMode(),
shopId=self.getServiceUsername(),
sequenceNb=1,
transmissionDate=transmissionDate,
)
kw['wsSignature'] = self._getSignature(kw, sorted_keys)
data = client.service.getInfo(**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)
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]
@setUTCTimeZone
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)
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]
@setUTCTimeZone
def soap_cancel(self, transmissionDate, transactionId, comment=''):
# prepare with passed parameters
kw = dict(transmissionDate=transmissionDate, transactionId=transactionId,
comment=comment)
signature_sorted_key_list= ['shopId', 'transmissionDate', 'transactionId',
'sequenceNb', 'ctxMode', 'comment']
kw.update(
ctxMode=self.getPayzenVadsCtxMode(),
shopId=self.getServiceUsername(),
sequenceNb=1,
orderId=transactionId,
)
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.cancel(**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)
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
sent_data = str(kw)
data_kw, received_data = self.callPayzenApi(URL, kw)
return [data_kw, sent_data, received_data]
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]
finally:
if present:
os.environ['TZ'] = tz
else:
del(os.environ['TZ'])
time.tzset()
from erp5.component.interface.IPaymentService import IPaymentService
class PayzenService(XMLObject, PayzenSOAP):
class PayzenService(XMLObject, PayzenREST):
meta_type = 'Payzen Service'
portal_type = 'Payzen Service'
......
......@@ -6,10 +6,22 @@
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>PayzenService</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.PayzenService</string> </value>
......@@ -24,6 +36,24 @@
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple>
<string>W: 24, 4: Redefining name \'tz\' from outer scope (line 15) (redefined-outer-name)</string>
<string>W: 23, 4: Redefining name \'present\' from outer scope (line 14) (redefined-outer-name)</string>
<string>W:190, 4: Unreachable code (unreachable)</string>
<string>W:195, 4: Unreachable code (unreachable)</string>
<string>W:200, 4: Unreachable code (unreachable)</string>
</tuple>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
......@@ -31,13 +61,28 @@
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
......@@ -50,7 +95,7 @@
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -59,7 +104,7 @@
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
......@@ -74,33 +119,6 @@
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>ERP5TypeTestCase</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1377844605.75</float>
<string>GMT+9</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>service_api_key_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>read_permission</string> </key>
<value> <string>Manage portal</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -106,6 +106,7 @@
<list>
<string>my_link_url_string</string>
<string>my_service_username</string>
<string>my_service_api_key</string>
<string>my_service_password</string>
<string>my_payzen_vads_ctx_mode</string>
<string>my_payzen_vads_version</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StringField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_service_api_key</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>too_long</string> </key>
<value> <string>Too much input was given.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_maxwidth</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Password</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -227,6 +227,10 @@
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
......@@ -237,7 +241,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Certificate</string> </value>
<value> <string>Key</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
......
......@@ -227,6 +227,10 @@
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>input_type</string> </key>
<value> <string>text</string> </value>
</item>
<item>
<key> <string>max_length</string> </key>
<value> <string></string> </value>
......@@ -237,7 +241,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>VADS Site ID</string> </value>
<value> <string>User</string> </value>
</item>
<item>
<key> <string>truncate</string> </key>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment