Commit 40fa9ff0 authored by Rafael Monnerat's avatar Rafael Monnerat

Payzen rest

See merge request !274
parents eb35deda 79de2138
"""
This script was introduced for backward compatibility on migration and for
introduce custom delays on what configures the expiration dates.
"""
from DateTime import DateTime
return int(DateTime()) - int(transaction_date) > 86400
...@@ -50,11 +50,11 @@ ...@@ -50,11 +50,11 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>state_change</string> </value> <value> <string>transaction_date</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>PayzenEvent_registerPayzen</string> </value> <value> <string>PayzenEvent_isPaymentExpired</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -13,104 +13,95 @@ transaction = payzen_event.getDestinationValue() ...@@ -13,104 +13,95 @@ transaction = payzen_event.getDestinationValue()
if transaction is None: if transaction is None:
raise ValueError("Unable to find related transaction") raise ValueError("Unable to find related transaction")
assert signature in (True, False) isTransitionPossible = context.getPortalObject().portal_workflow.isTransitionPossible
if signature is False:
# signature is wrong, bye bye status = data_kw['status']
payzen_event.confirm(comment='Signature does not match') answer = data_kw['answer']
if status != "SUCCESS":
error_code = answer["error_code"]
if error_code == "PSP_010":
# Transaction Not Found
# Mark on payment transaction history log that transaction was not processed yet
transaction_date, _ = transaction.PaymentTransaction_getPayzenId()
payzen_event.confirm()
payzen_event.acknowledge(comment='Transaction not found on payzen side.')
if context.PayzenEvent_isPaymentExpired(transaction_date):
if isTransitionPossible(transaction, 'cancel'):
transaction.cancel(comment='Aborting unknown payzen payment.')
else:
storeWorkflowComment(transaction,
'Error code PSP_010 (Not found) did not changed the document state.')
return
else:
# Unknown errorCode
payzen_event.confirm(comment='Unknown errorCode %r' % error_code)
return
transaction_list = answer["transactions"]
if len(transaction_list) != 1:
# Unexpected Number of Transactions
payzen_event.confirm(comment='Unexpected Number of Transaction for this order')
return return
isTransitionPossible = context.getPortalObject().portal_workflow.isTransitionPossible transaction_kw = transaction_list[0]
# See Full Status list at https://payzen.io/en-EN/rest/V4.0/api/kb/status_reference.html
mark_transaction_id_list = [
'AUTHORISED_TO_VALIDATE',
'WAITING_AUTHORISATION',
'WAITING_AUTHORISATION_TO_VALIDATE',
]
continue_transaction_id_list = ['AUTHORISED', 'CAPTURED', 'PARTIALLY_AUTHORISED']
cancel_transaction_id_list = ['REFUSED']
transaction_status = transaction_kw['detailedStatus']
error_code = data_kw['errorCode'] if transaction_status in mark_transaction_id_list:
if error_code == '2':
transaction_date, _ = transaction.PaymentTransaction_getPayzenId()
# Mark on payment transaction history log that transaction was not processed yet # Mark on payment transaction history log that transaction was not processed yet
storeWorkflowComment(transaction, 'Transaction status %s did not changed the document state' % (transaction_status))
payzen_event.confirm() payzen_event.confirm()
payzen_event.acknowledge(comment='Transaction not found on payzen side.') payzen_event.acknowledge(comment='Automatic acknowledge as result of correct communication')
if int(DateTime()) - int(transaction_date) > 86400: if isTransitionPossible(transaction, 'confirm'):
if isTransitionPossible(transaction, 'cancel'): transaction.confirm(comment='Confirmed as really saw in PayZen.')
transaction.cancel(comment='Aborting unknown payzen payment.')
else: elif transaction_status in continue_transaction_id_list:
storeWorkflowComment(transaction, # Check authAmount and authDevise and if match, stop transaction
'Error code 2 (Not found) did not changed the document state.') auth_amount = int(transaction_kw['transactionDetails']['cardDetails']['authorizationResponse']['amount'])
return auth_devise = transaction_kw['transactionDetails']['cardDetails']['authorizationResponse']['currency']
transaction_amount = int((round(transaction.PaymentTransaction_getTotalPayablePrice(), 2) * -100))
if transaction_amount != auth_amount:
payzen_event.confirm(comment='Received amount (%r) does not match stored on transaction (%r)'% (auth_amount, transaction_amount))
return
elif error_code == '0': transaction_devise = transaction.getResourceReference()
transaction_code_mapping = { if transaction_devise != auth_devise:
'0': 'Initial (being treated)', payzen_event.confirm(comment='Received devise (%r) does not match stored on transaction (%r)'% (auth_devise, transaction_devise))
'1': 'To be validated ',
'2': 'To be forced - Contact issuer',
'3': 'To be validated and authorized',
'4': 'Waiting for submission',
'5': 'Waiting for authorization',
'6': 'Submitted',
'7': 'Expired',
'8': 'Refused',
'9': 'Cancelled',
'10': 'Waiting',
'11': 'Being submitted',
'12': 'Being authorized',
'13': 'Failed',
}
mark_transaction_id_list = ['0', '1', '3', '5', '10', '11', '12']
continue_transaction_id_list = ['4', '6']
cancel_transaction_id_list = ['8']
transaction_status = data_kw['transactionStatus']
transaction_status_description = transaction_code_mapping.get(transaction_status, None)
if transaction_status_description is None:
payzen_event.confirm(comment='Unknown transactionStatus %r' % transaction_status)
return return
if transaction_status in mark_transaction_id_list: comment = 'PayZen considered as paid.'
# Mark on payment transaction history log that transaction was not processed yet if isTransitionPossible(transaction, 'confirm'):
storeWorkflowComment(transaction, 'Transaction status %s (%s) did not changed the document state' % (transaction_status, transaction_status_description)) transaction.confirm(comment=comment)
payzen_event.confirm() if isTransitionPossible(transaction, 'start'):
payzen_event.acknowledge(comment='Automatic acknowledge as result of correct communication') transaction.start(comment=comment)
if isTransitionPossible(transaction, 'confirm'): if isTransitionPossible(transaction, 'stop'):
transaction.confirm(comment='Confirmed as really saw in PayZen.') transaction.stop(comment=comment)
elif transaction_status in continue_transaction_id_list:
# Check authAmount and authDevise and if match, stop transaction
auth_amount = int(data_kw['authAmount'])
auth_devise = data_kw['authDevise']
transaction_amount = int((round(transaction.PaymentTransaction_getTotalPayablePrice(), 2) * -100))
if transaction_amount != auth_amount:
payzen_event.confirm(comment='Received amount (%r) does not match stored on transaction (%r)'% (auth_amount, transaction_amount))
return
transaction_devise = transaction.getResourceValue().Currency_getIntegrationMapping()
if transaction_devise != auth_devise:
payzen_event.confirm(comment='Received devise (%r) does not match stored on transaction (%r)'% (auth_devise, transaction_devise))
return
comment = 'PayZen considered as paid.'
if isTransitionPossible(transaction, 'confirm'):
transaction.confirm(comment=comment)
if isTransitionPossible(transaction, 'start'):
transaction.start(comment=comment)
if isTransitionPossible(transaction, 'stop'):
transaction.stop(comment=comment)
if transaction.getSimulationState() == 'stopped':
payzen_event.confirm()
payzen_event.acknowledge(comment='Automatic acknowledge as result of correct communication')
else:
payzen_event.confirm(comment='Expected to put transaction in stopped state, but achieved only %s state' % transaction.getSimulationState())
elif transaction_status in cancel_transaction_id_list: if transaction.getSimulationState() == 'stopped':
payzen_event.confirm() payzen_event.confirm()
payzen_event.acknowledge(comment='Refused payzen payment.') payzen_event.acknowledge(comment='Automatic acknowledge as result of correct communication')
if isTransitionPossible(transaction, 'cancel'):
transaction.cancel(comment='Aborting refused payzen payment.')
return
else: else:
payzen_event.confirm(comment='Transaction status %r (%r) is not supported' \ payzen_event.confirm(comment='Expected to put transaction in stopped state, but achieved only %s state' % transaction.getSimulationState())
% (transaction_status, transaction_status_description))
return
elif transaction_status in cancel_transaction_id_list:
payzen_event.confirm()
payzen_event.acknowledge(comment='Refused payzen payment.')
if isTransitionPossible(transaction, 'cancel'):
transaction.cancel(comment='Aborting refused payzen payment.')
return
else: else:
# Unknown errorCode payzen_event.confirm(comment='Transaction status %r is not supported' \
payzen_event.confirm(comment='Unknown errorCode %r' % error_code) % (transaction_status))
return
...@@ -50,7 +50,15 @@ ...@@ -50,7 +50,15 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>data_kw, signature, REQUEST=None</string> </value> <value> <string>data_kw, REQUEST=None</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -5,7 +5,7 @@ from DateTime import DateTime ...@@ -5,7 +5,7 @@ from DateTime import DateTime
from Products.ERP5Type.tests.utils import createZODBPythonScript from Products.ERP5Type.tests.utils import createZODBPythonScript
import difflib import difflib
HARDCODED_PRICE = 99.6 HARDCODED_PRICE = -99.6
vads_url_cancel = 'http://example.org/cancel' vads_url_cancel = 'http://example.org/cancel'
vads_url_error = 'http://example.org/error' vads_url_error = 'http://example.org/error'
...@@ -187,6 +187,7 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -187,6 +187,7 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'vads_amount': str(int(HARDCODED_PRICE * -100)), 'vads_amount': str(int(HARDCODED_PRICE * -100)),
'vads_currency': 978, 'vads_currency': 978,
'vads_trans_id': transaction_id, 'vads_trans_id': transaction_id,
'vads_order_id': transaction_id,
'vads_site_id': 'foo', 'vads_site_id': 'foo',
} }
# Calculate the signature... # Calculate the signature...
...@@ -201,10 +202,10 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -201,10 +202,10 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'%(vads_url_return)s">\n\n\n <input type="hidden" name="vads_site_id" '\ '%(vads_url_return)s">\n\n\n <input type="hidden" name="vads_site_id" '\
'value="%(vads_site_id)s">\n\n\n <input type="hidden" name="vads_url_e'\ 'value="%(vads_site_id)s">\n\n\n <input type="hidden" name="vads_url_e'\
'rror"\n value="%(vads_url_error)s">\n\n\n <input type="hidden'\ 'rror"\n value="%(vads_url_error)s">\n\n\n <input type="hidden'\
'" name="vads_trans_id" value="%(vads_trans_id)s">\n\n\n <input type="'\ '" name="vads_trans_id" value="%(vads_trans_id)s">\n\n\n '\
'hidden" name="vads_action_mode"\n value="INTERACTIVE">\n\n\n '\
'<input type="hidden" name="vads_url_success"\n value="'\ '<input type="hidden" name="vads_url_success"\n value="'\
'%(vads_url_success)s">\n\n\n <input type="hidden" name="vads_url_refe'\ '%(vads_url_success)s">\n\n\n <input type="hidden" name="vads_order_id'\
'" value="%(vads_trans_id)s">\n\n\n <input type="hidden" name="vads_url_refe'\
'rral"\n value="%(vads_url_referral)s">\n\n\n <input type="hid'\ 'rral"\n value="%(vads_url_referral)s">\n\n\n <input type="hid'\
'den" name="vads_page_action"\n value="PAYMENT">\n\n\n <input '\ 'den" name="vads_page_action"\n value="PAYMENT">\n\n\n <input '\
'type="hidden" name="vads_trans_date"\n value="'\ 'type="hidden" name="vads_trans_date"\n value="'\
...@@ -218,7 +219,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -218,7 +219,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'\n <input type="hidden" name="vads_language" value="%(vads_language)s">\n\n\n <inpu'\ '\n <input type="hidden" name="vads_language" value="%(vads_language)s">\n\n\n <inpu'\
't type="hidden" name="vads_currency" value="%(vads_currency)s">\n\n\n '\ 't type="hidden" name="vads_currency" value="%(vads_currency)s">\n\n\n '\
' <input type="hidden" name="vads_amount" value="%(vads_amount)s">\n\n\n'\ ' <input type="hidden" name="vads_amount" value="%(vads_amount)s">\n\n\n'\
' <input type="hidden" name="vads_version" value="V2">\n\n<center>\n '\ ' <input type="hidden" name="vads_version" value="V2">\n\n\n <input type="'\
'hidden" name="vads_action_mode"\n value="INTERACTIVE">\n\n<center>\n '\
' <input type="submit" value="Click to pay">\n</center>\n</form>' % data_dict ' <input type="submit" value="Click to pay">\n</center>\n</form>' % data_dict
# Event message state # Event message state
...@@ -252,17 +254,17 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -252,17 +254,17 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
_ , _ = payment.PaymentTransaction_generatePayzenId() _ , _ = payment.PaymentTransaction_generatePayzenId()
self.assertRaises(AttributeError, event.updateStatus) self.assertRaises(AttributeError, event.updateStatus)
def mockSoapGetInfo(self, method_to_call, expected_args, result_tuple): def mockRestGetInfo(self, method_to_call, expected_args, result_tuple):
payment_service = self.portal.portal_secure_payments.slapos_payzen_test payment_service = self.portal.portal_secure_payments.slapos_payzen_test
def mocksoad_getInfo(arg1, arg2): def mockrest_getInfo(arg1, arg2):
self.assertEqual(arg1, expected_args[0]) self.assertEqual(arg1, expected_args[0])
self.assertEqual(arg2, expected_args[1]) self.assertEqual(arg2, expected_args[1])
return result_tuple return result_tuple
setattr(payment_service, 'soap_getInfo', mocksoad_getInfo) setattr(payment_service, 'rest_getInfo', mockrest_getInfo)
try: try:
return method_to_call() return method_to_call()
finally: finally:
del payment_service.soap_getInfo del payment_service.rest_getInfo
def _simulatePayzenEvent_processUpdate(self): def _simulatePayzenEvent_processUpdate(self):
script_name = 'PayzenEvent_processUpdate' script_name = 'PayzenEvent_processUpdate'
...@@ -293,16 +295,15 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P ...@@ -293,16 +295,15 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
payment.PaymentTransaction_generatePayzenId() payment.PaymentTransaction_generatePayzenId()
mocked_data_kw = 'mocked_data_kw' mocked_data_kw = 'mocked_data_kw'
mocked_signature = 'mocked_signature'
mocked_sent_text = 'mocked_sent_text' mocked_sent_text = 'mocked_sent_text'
mocked_received_text = 'mocked_received_text' mocked_received_text = 'mocked_received_text'
self._simulatePayzenEvent_processUpdate() self._simulatePayzenEvent_processUpdate()
try: try:
self.mockSoapGetInfo( self.mockRestGetInfo(
event.updateStatus, event.updateStatus,
(transaction_date.toZone('UTC').asdatetime(), transaction_id), (transaction_date.toZone('UTC').asdatetime(), transaction_id),
(mocked_data_kw, mocked_signature, mocked_sent_text, mocked_received_text), (mocked_data_kw, mocked_sent_text, mocked_received_text),
) )
finally: finally:
self._dropPayzenEvent_processUpdate() self._dropPayzenEvent_processUpdate()
...@@ -311,11 +312,11 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P ...@@ -311,11 +312,11 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
self.assertEqual(len(event_message_list), 2) self.assertEqual(len(event_message_list), 2)
sent_message = [x for x in event_message_list \ sent_message = [x for x in event_message_list \
if x.getTitle() == 'Sent SOAP'][0] if x.getTitle() == 'Sent Data'][0]
self.assertEqual(sent_message.getTextContent(), mocked_sent_text) self.assertEqual(sent_message.getTextContent(), mocked_sent_text)
received_message = [x for x in event_message_list \ received_message = [x for x in event_message_list \
if x.getTitle() == 'Received SOAP'][0] if x.getTitle() == 'Received Data'][0]
self.assertEqual(received_message.getPredecessor(), self.assertEqual(received_message.getPredecessor(),
sent_message.getRelativeUrl()) sent_message.getRelativeUrl())
self.assertEqual(received_message.getTextContent(), mocked_received_text) self.assertEqual(received_message.getTextContent(), mocked_received_text)
......
...@@ -29,6 +29,7 @@ payzen_dict = { ...@@ -29,6 +29,7 @@ payzen_dict = {
'vads_amount': str(int(round((payment_transaction.PaymentTransaction_getTotalPayablePrice() * -100), 0))), 'vads_amount': str(int(round((payment_transaction.PaymentTransaction_getTotalPayablePrice() * -100), 0))),
'vads_trans_date': now.toZone('UTC').asdatetime().strftime('%Y%m%d%H%M%S'), 'vads_trans_date': now.toZone('UTC').asdatetime().strftime('%Y%m%d%H%M%S'),
'vads_trans_id': transaction_id, 'vads_trans_id': transaction_id,
'vads_order_id': transaction_id,
'vads_language': 'en', 'vads_language': 'en',
'vads_url_cancel': vads_url_cancel, 'vads_url_cancel': vads_url_cancel,
'vads_url_error': vads_url_error, 'vads_url_error': vads_url_error,
......
"""Registers current transaction in payment
In order to not transmit sensitive information the registration is done by looking the newest
payzen related transaction for destination_section and doing its duplicate"""
from DateTime import DateTime
payzen_event = state_change['object']
transaction = payzen_event.getDestinationValue()
payment_service = payzen_event.getSourceValue(portal_type="Payzen Service")
previous_id = transaction.PaymentTransaction_getPreviousPayzenId()
if previous_id is None:
payzen_event.confirm(comment='No previous id found')
return
transaction_date, transaction_id = transaction.PaymentTransaction_generatePayzenId()
if transaction_id is None:
raise ValueError('Transaction already mapped in integration tool.')
# do causality mapping in integration_site between transaction.getRelativeUrl and today + transaction_id
payzen_dict = {}
payzen_dict.update(
devise=transaction.getResourceValue().Currency_getIntegrationMapping(),
amount=str(int(round((transaction.PaymentTransaction_getTotalPayablePrice() * -100), 0))),
presentationDate=transaction.getStartDate().toZone('UTC').asdatetime(),
newTransactionId=transaction_id,
transmissionDate=transaction_date.asdatetime(),
transactionId=previous_id
)
data_kw, signature, sent_text, received_text = payment_service.soap_duplicate(**payzen_dict)
# SENT
sent = payzen_event.newContent(title='Sent SOAP',
portal_type='Payzen Event Message',
text_content=sent_text)
# RECEIVED
payzen_event.newContent(title='Received SOAP',
text_content=received_text,
predecessor_value=sent,
portal_type='Payzen Event Message')
context.PayzenEvent_processUpdate(state_change, data_kw, signature)
...@@ -6,20 +6,20 @@ if transaction_id is None: ...@@ -6,20 +6,20 @@ if transaction_id is None:
raise ValueError('Transaction not registered in payzen integration tool') raise ValueError('Transaction not registered in payzen integration tool')
payment_service = payzen_event.getSourceValue(portal_type="Payzen Service") payment_service = payzen_event.getSourceValue(portal_type="Payzen Service")
data_kw, signature, sent_text, received_text = payment_service.soap_getInfo( data_kw, sent_text, received_text = payment_service.rest_getInfo(
transaction_date.toZone('UTC').asdatetime(), transaction_date.toZone('UTC').asdatetime(),
transaction_id) transaction_id)
# SENT # SENT
sent = payzen_event.newContent( sent = payzen_event.newContent(
title='Sent SOAP', title='Sent Data',
portal_type='Payzen Event Message', portal_type='Payzen Event Message',
text_content=sent_text) text_content=sent_text)
# RECEIVED # RECEIVED
payzen_event.newContent( payzen_event.newContent(
title='Received SOAP', title='Received Data',
portal_type='Payzen Event Message', portal_type='Payzen Event Message',
text_content=received_text, text_content=received_text,
predecessor_value=sent) predecessor_value=sent)
payzen_event.PayzenEvent_processUpdate(data_kw, signature) payzen_event.PayzenEvent_processUpdate(data_kw)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>PayzenEvent_registerPayzen</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>register_payzen</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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