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

Payzen rest

See merge request nexedi/slapos.core!274
parents eb35deda 79de2138
Pipeline #12980 failed with stage
in 0 seconds
"""
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 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
<value> <string>transaction_date</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PayzenEvent_registerPayzen</string> </value>
<value> <string>PayzenEvent_isPaymentExpired</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -13,104 +13,95 @@ transaction = payzen_event.getDestinationValue()
if transaction is None:
raise ValueError("Unable to find related transaction")
assert signature in (True, False)
if signature is False:
# signature is wrong, bye bye
payzen_event.confirm(comment='Signature does not match')
isTransitionPossible = context.getPortalObject().portal_workflow.isTransitionPossible
status = data_kw['status']
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
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 error_code == '2':
transaction_date, _ = transaction.PaymentTransaction_getPayzenId()
if transaction_status in mark_transaction_id_list:
# 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.acknowledge(comment='Transaction not found on payzen side.')
if int(DateTime()) - int(transaction_date) > 86400:
if isTransitionPossible(transaction, 'cancel'):
transaction.cancel(comment='Aborting unknown payzen payment.')
else:
storeWorkflowComment(transaction,
'Error code 2 (Not found) did not changed the document state.')
return
payzen_event.acknowledge(comment='Automatic acknowledge as result of correct communication')
if isTransitionPossible(transaction, 'confirm'):
transaction.confirm(comment='Confirmed as really saw in PayZen.')
elif transaction_status in continue_transaction_id_list:
# Check authAmount and authDevise and if match, stop transaction
auth_amount = int(transaction_kw['transactionDetails']['cardDetails']['authorizationResponse']['amount'])
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_code_mapping = {
'0': 'Initial (being treated)',
'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)
transaction_devise = transaction.getResourceReference()
if transaction_devise != auth_devise:
payzen_event.confirm(comment='Received devise (%r) does not match stored on transaction (%r)'% (auth_devise, transaction_devise))
return
if transaction_status in mark_transaction_id_list:
# Mark on payment transaction history log that transaction was not processed yet
storeWorkflowComment(transaction, 'Transaction status %s (%s) did not changed the document state' % (transaction_status, transaction_status_description))
payzen_event.confirm()
payzen_event.acknowledge(comment='Automatic acknowledge as result of correct communication')
if isTransitionPossible(transaction, 'confirm'):
transaction.confirm(comment='Confirmed as really saw in PayZen.')
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())
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)
elif transaction_status in cancel_transaction_id_list:
if transaction.getSimulationState() == 'stopped':
payzen_event.confirm()
payzen_event.acknowledge(comment='Refused payzen payment.')
if isTransitionPossible(transaction, 'cancel'):
transaction.cancel(comment='Aborting refused payzen payment.')
return
payzen_event.acknowledge(comment='Automatic acknowledge as result of correct communication')
else:
payzen_event.confirm(comment='Transaction status %r (%r) is not supported' \
% (transaction_status, transaction_status_description))
return
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:
payzen_event.confirm()
payzen_event.acknowledge(comment='Refused payzen payment.')
if isTransitionPossible(transaction, 'cancel'):
transaction.cancel(comment='Aborting refused payzen payment.')
return
else:
# Unknown errorCode
payzen_event.confirm(comment='Unknown errorCode %r' % error_code)
payzen_event.confirm(comment='Transaction status %r is not supported' \
% (transaction_status))
return
......@@ -50,7 +50,15 @@
</item>
<item>
<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>
<key> <string>id</string> </key>
......
......@@ -5,7 +5,7 @@ from DateTime import DateTime
from Products.ERP5Type.tests.utils import createZODBPythonScript
import difflib
HARDCODED_PRICE = 99.6
HARDCODED_PRICE = -99.6
vads_url_cancel = 'http://example.org/cancel'
vads_url_error = 'http://example.org/error'
......@@ -187,6 +187,7 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'vads_amount': str(int(HARDCODED_PRICE * -100)),
'vads_currency': 978,
'vads_trans_id': transaction_id,
'vads_order_id': transaction_id,
'vads_site_id': 'foo',
}
# Calculate the signature...
......@@ -201,10 +202,10 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'%(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'\
'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="'\
'hidden" name="vads_action_mode"\n value="INTERACTIVE">\n\n\n '\
'" name="vads_trans_id" value="%(vads_trans_id)s">\n\n\n '\
'<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'\
'den" name="vads_page_action"\n value="PAYMENT">\n\n\n <input '\
'type="hidden" name="vads_trans_date"\n value="'\
......@@ -218,7 +219,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'\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 '\
' <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
# Event message state
......@@ -252,17 +254,17 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
_ , _ = payment.PaymentTransaction_generatePayzenId()
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
def mocksoad_getInfo(arg1, arg2):
def mockrest_getInfo(arg1, arg2):
self.assertEqual(arg1, expected_args[0])
self.assertEqual(arg2, expected_args[1])
return result_tuple
setattr(payment_service, 'soap_getInfo', mocksoad_getInfo)
setattr(payment_service, 'rest_getInfo', mockrest_getInfo)
try:
return method_to_call()
finally:
del payment_service.soap_getInfo
del payment_service.rest_getInfo
def _simulatePayzenEvent_processUpdate(self):
script_name = 'PayzenEvent_processUpdate'
......@@ -293,16 +295,15 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
payment.PaymentTransaction_generatePayzenId()
mocked_data_kw = 'mocked_data_kw'
mocked_signature = 'mocked_signature'
mocked_sent_text = 'mocked_sent_text'
mocked_received_text = 'mocked_received_text'
self._simulatePayzenEvent_processUpdate()
try:
self.mockSoapGetInfo(
self.mockRestGetInfo(
event.updateStatus,
(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:
self._dropPayzenEvent_processUpdate()
......@@ -311,11 +312,11 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
self.assertEqual(len(event_message_list), 2)
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)
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(),
sent_message.getRelativeUrl())
self.assertEqual(received_message.getTextContent(), mocked_received_text)
......
......@@ -29,6 +29,7 @@ payzen_dict = {
'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_id': transaction_id,
'vads_order_id': transaction_id,
'vads_language': 'en',
'vads_url_cancel': vads_url_cancel,
'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:
raise ValueError('Transaction not registered in payzen integration tool')
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_id)
# SENT
sent = payzen_event.newContent(
title='Sent SOAP',
title='Sent Data',
portal_type='Payzen Event Message',
text_content=sent_text)
# RECEIVED
payzen_event.newContent(
title='Received SOAP',
title='Received Data',
portal_type='Payzen Event Message',
text_content=received_text,
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