Commit 3aca11ec authored by Rafael Monnerat's avatar Rafael Monnerat

Pay deposity for payable subscription requests

See merge request !629
parents 0941e532 cb300deb
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
from DateTime import DateTime from DateTime import DateTime
from Products.ERP5Type.Message import translateString from Products.ERP5Type.Message import translateString
portal = context.getPortalObject() portal = context.getPortalObject()
if not subscription_list:
raise ValueError('You need to provide at least one Subscription Request')
payment_tag = 'Entity_addDepositPayment_%s' % context.getUid()
if context.REQUEST.get(payment_tag, None) is not None:
raise ValueError('This script was already called twice on the same transaction ')
activate_kw = {
'tag': payment_tag
}
# Ensure all invoice use the same arrow and resource
first_subscription = subscription_list[0]
identical_dict = {
'getSource': first_subscription.getSource(),
'getSourceSection': first_subscription.getSourceSection(),
'getDestinationSection': first_subscription.getDestinationSection(),
'getPriceCurrency': first_subscription.getPriceCurrency(),
'getLedger': first_subscription.getLedger(),
}
price = 0
for subscription in subscription_list:
for method_id, method_value in identical_dict.items():
if getattr(subscription, method_id)() != method_value:
raise ValueError('Subscription Requests do not match on method: %s' % method_id)
if subscription.total_price:
price += subscription.total_price
# Simulation state
if not subscription.isTempObject() and subscription.getSimulationState() != "submitted":
raise ValueError('Not on submitted state')
if subscription.getPortalType() != "Subscription Request":
raise ValueError('Not an Subscription Request')
if not price:
raise ValueError("No price to to pay")
if first_subscription.getDestinationSection() != context.getRelativeUrl():
raise ValueError("Subscription not related to the context")
###################################################### ######################################################
# Find Sale Trade Condition # Find Sale Trade Condition
source_section = context source_section = context
currency_relative_url = first_subscription.getPriceCurrency()
ledger_relative_url = first_subscription.getLedger()
# Create a temp Sale Order to calculate the real price and find the trade condition # Create a temp Sale Order to calculate the real price and find the trade condition
now = DateTime() now = DateTime()
module = portal.portal_trash module = portal.portal_trash
tmp_sale_order = module.newContent( tmp_sale_order = module.newContent(
portal_type='Sale Order', portal_type='Sale Order',
temp_object=True, temp_object=True,
trade_condition_type="deposit", trade_condition_type="deposit",
start_date=now, start_date=now,
source=first_subscription.getSource(),
source_section=first_subscription.getSourceSection(),
destination_value=source_section, destination_value=source_section,
destination_section_value=source_section, destination_section_value=source_section,
destination_decision_value=source_section, destination_decision_value=source_section,
ledger_value=portal.portal_categories.ledger.automated, ledger=ledger_relative_url,
price_currency=currency_relative_url price_currency=currency_relative_url
) )
tmp_sale_order.SaleOrder_applySaleTradeCondition(batch_mode=1, force=1) tmp_sale_order.SaleOrder_applySaleTradeCondition(batch_mode=1, force=1)
...@@ -36,14 +84,20 @@ if (tmp_sale_order.getSourceSection(None) == tmp_sale_order.getDestinationSectio ...@@ -36,14 +84,20 @@ if (tmp_sale_order.getSourceSection(None) == tmp_sale_order.getDestinationSectio
(tmp_sale_order.getSourceSection(None) is None): (tmp_sale_order.getSourceSection(None) is None):
raise AssertionError('The trade condition does not generate accounting: %s' % tmp_sale_order.getSpecialise()) raise AssertionError('The trade condition does not generate accounting: %s' % tmp_sale_order.getSpecialise())
####################################################### #######################################################
payment_transaction = portal.accounting_module.newContent( # The payment needs to be returned on web site context, to proper handle acquisition later on
# otherwise, payment redirections would fail on multiple occasions whenever website isn't in
# the context acquisition.
web_site = context.getWebSiteValue()
if web_site is None:
web_site = portal
# preserve the capability to call payment_transaction.getWebSiteValue() and get the current website back.
payment_transaction = web_site.accounting_module.newContent(
title="reservation payment", title="reservation payment",
portal_type="Payment Transaction", portal_type="Payment Transaction",
start_date=now, start_date=now,
stop_date=now, stop_date=now,
specialise_value=tmp_sale_order.getSpecialiseValue(), specialise_value=tmp_sale_order.getSpecialiseValue(),
source=tmp_sale_order.getSource(), source=tmp_sale_order.getSource(),
source_section=tmp_sale_order.getSourceSection(), source_section=tmp_sale_order.getSourceSection(),
...@@ -53,11 +107,11 @@ payment_transaction = portal.accounting_module.newContent( ...@@ -53,11 +107,11 @@ payment_transaction = portal.accounting_module.newContent(
destination_section=tmp_sale_order.getDestinationSection(), destination_section=tmp_sale_order.getDestinationSection(),
destination_decision=tmp_sale_order.getDestinationDecision(), destination_decision=tmp_sale_order.getDestinationDecision(),
destination_project=tmp_sale_order.getDestinationProject(), destination_project=tmp_sale_order.getDestinationProject(),
payment_mode=payment_mode,
ledger_value=portal.portal_categories.ledger.automated, ledger_value=ledger_relative_url,
resource=tmp_sale_order.getPriceCurrency(), resource=tmp_sale_order.getPriceCurrency(),
created_by_builder=1, # XXX this prevent init script from creating lines. created_by_builder=1, # XXX this prevent init script from creating lines.
activate_kw={'tag':'%s_init' % context.getRelativeUrl()} activate_kw=activate_kw
) )
getAccountForUse = context.Base_getAccountForUse getAccountForUse = context.Base_getAccountForUse
...@@ -68,7 +122,8 @@ payment_transaction.newContent( ...@@ -68,7 +122,8 @@ payment_transaction.newContent(
portal_type='Accounting Transaction Line', portal_type='Accounting Transaction Line',
quantity=price, quantity=price,
source_value=getAccountForUse('asset_receivable_subscriber'), source_value=getAccountForUse('asset_receivable_subscriber'),
destination_value=getAccountForUse('payable') destination_value=getAccountForUse('payable'),
activate_kw=activate_kw
) )
# bank # bank
...@@ -78,15 +133,16 @@ payment_transaction.newContent( ...@@ -78,15 +133,16 @@ payment_transaction.newContent(
portal_type='Accounting Transaction Line', portal_type='Accounting Transaction Line',
quantity=-price, quantity=-price,
source_value=collection_account, source_value=collection_account,
destination_value=collection_account destination_value=collection_account,
activate_kw=activate_kw
) )
if len(payment_transaction.checkConsistency()) != 0: if len(payment_transaction.checkConsistency()) != 0:
raise AssertionError(payment_transaction.checkConsistency()[0]) raise AssertionError(payment_transaction.checkConsistency()[0])
#tag = '%s_update' % context.getDestinationReference() payment_transaction.start(comment=translateString("Deposit payment."))
comment = translateString("Deposit payment.") # Set a flag on the request for prevent 2 calls on the same transaction
payment_transaction.start(comment=comment) context.REQUEST.set(payment_tag, 1)
return payment_transaction return payment_transaction
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>subscription_list, payment_mode=None, REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Entity_createDepositPaymentTransaction</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -90,7 +90,8 @@ for index, line in enumerate(invoice_list): ...@@ -90,7 +90,8 @@ for index, line in enumerate(invoice_list):
activate_kw=activate_kw, activate_kw=activate_kw,
) )
assert len(payment_transaction.checkConsistency()) == 0 if len(payment_transaction.checkConsistency()) != 0:
raise AssertionError(payment_transaction.checkConsistency()[0])
payment_transaction.start() payment_transaction.start()
......
portal = context.getPortalObject()
# Ensure all invoice use the same arrow and resource
first_subscription = subscription_list[0]
identical_dict = {
'getSourceSection': first_subscription.getSourceSection(),
'getDestinationSection': first_subscription.getDestinationSection(),
'getPriceCurrency': first_subscription.getPriceCurrency(),
'getLedger': first_subscription.getLedger(),
}
for subscription in subscription_list:
for method_id, method_value in identical_dict.items():
if getattr(subscription, method_id)() != method_value:
raise ValueError('Subscription Requests do not match on method: %s' % method_id)
if subscription.getPortalType() != "Subscription Request":
raise ValueError('Not an Subscription Request')
assert_price_kw = {
'resource_uid': first_subscription.getPriceCurrencyUid(),
'portal_type': portal.getPortalAccountingMovementTypeList(),
'ledger_uid': first_subscription.getLedgerUid(),
}
if first_subscription.getDestinationSection() != context.getRelativeUrl():
raise ValueError("Subscription not related to the context")
# entity is the depositor
# mirror_section_uid is the payee/recipient
entity_uid = context.getUid()
mirror_section_uid = first_subscription.getSourceSectionUid()
# Total received
deposit_amount = portal.portal_simulation.getInventoryAssetPrice(
section_uid=entity_uid,
mirror_section_uid=mirror_section_uid,
mirror_node_uid=portal.restrictedTraverse('account_module/deposit_received').getUid(),
#node_category_strict_membership=['account_type/income'],
simulation_state= ('stopped', 'delivered'),
# Do not gather deposit reimburse
# when it does not yet have a grouping_reference
omit_asset_decrease=1,
grouping_reference=None,
**assert_price_kw
)
# Total reserved
payable_amount = portal.portal_simulation.getInventoryAssetPrice(
mirror_section_uid=entity_uid,
section_uid=mirror_section_uid,
# Do not gather deposit receivable
# when it does not yet have a grouping_reference
omit_asset_decrease=1,
node_category_strict_membership=['account_type/asset/receivable',
'account_type/liability/payable'],
simulation_state= ('planned', 'confirmed', 'started', 'stopped', 'delivered'),
grouping_reference=None,
**assert_price_kw
)
return deposit_amount - payable_amount
...@@ -50,11 +50,11 @@ ...@@ -50,11 +50,11 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>price, currency_relative_url, batch=0</string> </value> <value> <string>subscription_list, **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_addDepositPayment</string> </value> <value> <string>Entity_getDepositBalanceAmount</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
portal = context.getPortalObject()
query_kw = {
"portal_type": "Subscription Request",
"simulation_state": "submitted"
}
if section_uid:
query_kw['source_section__uid'] = section_uid
if ledger_uid:
query_kw['ledger__uid'] = ledger_uid
if resource_uid is not None:
query_kw['price_currency__uid'] = resource_uid
object_dict = {}
for subscription_request_brain in portal.portal_catalog(
destination_section__uid=context.getUid(),
**query_kw):
subscription_request = subscription_request_brain.getObject()
subscription_request_total_price = subscription_request.getTotalPrice()
if 0 < subscription_request_total_price:
currency_uid = subscription_request.getPriceCurrencyUid()
# Future proof in case we implement B2B payment
object_index = "%s_%s_%s" % (
currency_uid,
subscription_request.getSourceSection(),
subscription_request.getLedger())
if object_index not in object_dict:
object_dict[object_index] = [subscription_request, subscription_request_total_price]
else:
subscription_request_total_price += object_dict[object_index][1]
object_dict[object_index] = [object_dict[object_index][0],
subscription_request_total_price]
return [s.asContext(total_price=price) for s, price in object_dict.values()]
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>resource_uid=None, section_uid=None, ledger_uid=None,**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Entity_getOutstandingDepositAmountList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -34,6 +34,34 @@ from zExceptions import Unauthorized ...@@ -34,6 +34,34 @@ from zExceptions import Unauthorized
class TestSlapOSAccounting(SlapOSTestCaseMixin): class TestSlapOSAccounting(SlapOSTestCaseMixin):
def createIntegrationSite(self):
# Include a simple Integration site, which is required for
# PaymentTransaction_generatePayzenId
integration_site = self.portal.portal_integrations.newContent(
title="Integration site for test_AccountingTransaction_getPaymentState_payzen_waiting_payment",
reference="payzen",
portal_type="Integration Site"
)
integration_site.newContent(
id="Causality",
portal_type="Integration Base Category Mapping",
default_source_reference="Causality",
default_destination_reference="causality"
)
resource_map = integration_site.newContent(
id="Resource",
portal_type="Integration Base Category Mapping",
default_source_reference="Resource",
default_destination_reference="resource"
)
resource_map.newContent(
id='978',
portal_type="Integration Category Mapping",
default_destination_reference='resource/currency_module/EUR',
default_source_reference='978'
)
return integration_site
def createHostingSubscription(self): def createHostingSubscription(self):
new_id = self.generateNewId() new_id = self.generateNewId()
return self.portal.hosting_subscription_module.newContent( return self.portal.hosting_subscription_module.newContent(
...@@ -157,12 +185,28 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin): ...@@ -157,12 +185,28 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
) )
self.portal.portal_workflow._jumpToStateFor(payment, 'started') self.portal.portal_workflow._jumpToStateFor(payment, 'started')
system_preference = self.portal.portal_preferences.slapos_default_system_preference
older_integration_site = system_preference.getPreferredPayzenIntegrationSite()
integration_site = self.createIntegrationSite()
system_preference.setPreferredPayzenIntegrationSite(
integration_site.getRelativeUrl()
)
try:
self.tic() self.tic()
payment.PaymentTransaction_generatePayzenId() payment.PaymentTransaction_generatePayzenId()
self.assertRaises( self.assertRaises(
ValueError, ValueError,
invoice.SaleInvoiceTransaction_createReversalSaleInvoiceTransaction, invoice.SaleInvoiceTransaction_createReversalSaleInvoiceTransaction,
batch_mode=1) batch_mode=1)
finally:
self.portal.portal_integrations.manage_delObjects(
ids=[integration_site.getId()])
system_preference.setPreferredPayzenIntegrationSite(
older_integration_site
)
@withAbort @withAbort
def test_createReversalSaleInvoiceTransaction_ok(self, payment_mode='payzen'): def test_createReversalSaleInvoiceTransaction_ok(self, payment_mode='payzen'):
...@@ -391,11 +435,27 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin): ...@@ -391,11 +435,27 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
created_by_builder=1 # to prevent init script to create lines created_by_builder=1 # to prevent init script to create lines
) )
self.portal.portal_workflow._jumpToStateFor(payment, 'started') self.portal.portal_workflow._jumpToStateFor(payment, 'started')
system_preference = self.portal.portal_preferences.slapos_default_system_preference
older_integration_site = system_preference.getPreferredPayzenIntegrationSite()
integration_site = self.createIntegrationSite()
system_preference.setPreferredPayzenIntegrationSite(
integration_site.getRelativeUrl()
)
try:
payment.PaymentTransaction_generatePayzenId() payment.PaymentTransaction_generatePayzenId()
self.tic() self.tic()
self.login(person.getUserId()) self.login(person.getUserId())
self.assertEqual("Waiting for payment confirmation", self.assertEqual("Waiting for payment confirmation",
invoice.AccountingTransaction_getPaymentState()) invoice.AccountingTransaction_getPaymentState())
finally:
self.portal.portal_integrations.manage_delObjects(
ids=[integration_site.getId()])
system_preference.setPreferredPayzenIntegrationSite(
older_integration_site
)
def test_AccountingTransaction_getPaymentState_wechat_waiting_payment(self): def test_AccountingTransaction_getPaymentState_wechat_waiting_payment(self):
project = self.addProject() project = self.addProject()
......
...@@ -17,7 +17,7 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin): ...@@ -17,7 +17,7 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
User does not pay the subscription, which is cancelled after some time User does not pay the subscription, which is cancelled after some time
""" """
with PinnedDateTime(self, DateTime('2020/05/19')): with PinnedDateTime(self, DateTime('2020/05/19')):
owner_person, currency, project = self.bootstrapAccountingTest() owner_person, _, project = self.bootstrapAccountingTest()
# Ensure no unexpected object has been created # Ensure no unexpected object has been created
# 2 assignment # 2 assignment
# 2 sale trade condition # 2 sale trade condition
...@@ -32,9 +32,14 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin): ...@@ -32,9 +32,14 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
aggregate__uid=project.getUid() aggregate__uid=project.getUid()
) )
self.assertEqual(subscription_request.getSimulationState(), "submitted") self.assertEqual(subscription_request.getSimulationState(), "submitted")
deposit_outstanding_amount_list = owner_person.Entity_getOutstandingDepositAmountList()
self.assertEqual(len(deposit_outstanding_amount_list), 1)
self.assertEqual(subscription_request.getUid(),
deposit_outstanding_amount_list[0].getUid())
with PinnedDateTime(self, DateTime('2021/04/04')): with PinnedDateTime(self, DateTime('2021/04/04')):
payment_transaction = owner_person.Person_addDepositPayment(99*10, currency.getRelativeUrl(), 1) payment_transaction = owner_person.Entity_createDepositPaymentTransaction(
deposit_outstanding_amount_list)
# payzen interface will only stop the payment # payzen interface will only stop the payment
payment_transaction.stop() payment_transaction.stop()
self.tic() self.tic()
...@@ -209,9 +214,24 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin): ...@@ -209,9 +214,24 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
# Add deposit # Add deposit
with PinnedDateTime(self, creation_date + 2): with PinnedDateTime(self, creation_date + 2):
for person in person_list: for person in person_list:
payment_transaction = person.Person_addDepositPayment(99*100, currency.getRelativeUrl(), 1) # Just add some large sum, so instances dont get blocked.
tmp_subscription_request = self.portal.portal_trash.newContent(
portal_type='Subscription Request',
temp_object=True,
start_date=DateTime(),
# source_section rely on default trade condition, like the rest.
destination_value=person,
destination_section_value=person,
ledger_value=self.portal.portal_categories.ledger.automated,
price_currency=currency.getRelativeUrl(),
total_price=99 * 10
)
payment_transaction = person.Entity_createDepositPaymentTransaction(
[tmp_subscription_request])
# payzen interface will only stop the payment # payzen interface will only stop the payment
payment_transaction.stop() payment_transaction.stop()
self.tic()
################################################## ##################################################
# Add first batch of service, and generate invoices # Add first batch of service, and generate invoices
...@@ -299,7 +319,7 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin): ...@@ -299,7 +319,7 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
""" """
creation_date = DateTime('2020/02/19') creation_date = DateTime('2020/02/19')
with PinnedDateTime(self, creation_date): with PinnedDateTime(self, creation_date):
owner_person, currency, project = self.bootstrapAccountingTest() owner_person, _, project = self.bootstrapAccountingTest()
owner_person.edit(default_address_region='america/south/brazil') owner_person.edit(default_address_region='america/south/brazil')
# Ensure no unexpected object has been created # Ensure no unexpected object has been created
...@@ -310,10 +330,16 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin): ...@@ -310,10 +330,16 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
################################################## ##################################################
# Add deposit (0.1 to prevent discount generation) # Add deposit (0.1 to prevent discount generation)
deposit_outstanding_amount_list = owner_person.Entity_getOutstandingDepositAmountList()
self.assertEqual(len(deposit_outstanding_amount_list), 1)
self.assertEqual(sum([i.total_price for i in deposit_outstanding_amount_list]), 42)
with PinnedDateTime(self, creation_date + 0.1): with PinnedDateTime(self, creation_date + 0.1):
payment_transaction = owner_person.Person_addDepositPayment(99*100, currency.getRelativeUrl(), 1) payment_transaction = owner_person.Entity_createDepositPaymentTransaction(
deposit_outstanding_amount_list)
# payzen interface will only stop the payment # payzen interface will only stop the payment
payment_transaction.stop() payment_transaction.stop()
self.tic()
self.logout() self.logout()
self.login() self.login()
......
...@@ -245,14 +245,6 @@ ...@@ -245,14 +245,6 @@
<key> <string>preferred_password_lifetime_expire_warning_duration</string> </key> <key> <string>preferred_password_lifetime_expire_warning_duration</string> </key>
<value> <int>336</int> </value> <value> <int>336</int> </value>
</item> </item>
<item>
<key> <string>preferred_payzen_integration_site</string> </key>
<value> <string>portal_integrations/slapos_payzen_test_integration</string> </value>
</item>
<item>
<key> <string>preferred_payzen_payment_service_reference</string> </key>
<value> <string>PSERV-Payzen-Test</string> </value>
</item>
<item> <item>
<key> <string>preferred_registration_resource</string> </key> <key> <string>preferred_registration_resource</string> </key>
<value> <string>service_module/vifib_registration</string> </value> <value> <string>service_module/vifib_registration</string> </value>
......
...@@ -581,15 +581,17 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin): ...@@ -581,15 +581,17 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
if q.getTitle() == instance_title] if q.getTitle() == instance_title]
self.assertEqual(0, len(instance_tree_list)) self.assertEqual(0, len(instance_tree_list))
def checkServiceSubscriptionRequest(self, service): def checkServiceSubscriptionRequest(self, service, simulation_state='invalidated'):
self.login() self.login()
subscription_request = self.portal.portal_catalog.getResultValue( subscription_request = self.portal.portal_catalog.getResultValue(
portal_type="Subscription Request", portal_type="Subscription Request",
aggregate__uid=service.getUid(), aggregate__uid=service.getUid(),
simulation_state='invalidated' simulation_state=simulation_state
) )
self.assertNotEqual(subscription_request, None) self.assertNotEqual(subscription_request, None,
"Not found subscription request for %s" % service.getRelativeUrl())
return subscription_request
def checkInstanceAllocation(self, person_user_id, person_reference, def checkInstanceAllocation(self, person_user_id, person_reference,
instance_title, software_release, software_type, server, instance_title, software_release, software_type, server,
...@@ -644,6 +646,96 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin): ...@@ -644,6 +646,96 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
partition.contentValues(portal_type='Internet Protocol Address')], partition.contentValues(portal_type='Internet Protocol Address')],
connection_dict.values()) connection_dict.values())
def checkInstanceAllocationWithDeposit(self, person_user_id, person_reference,
instance_title, software_release, software_type, server,
project_reference, deposit_amount, currency):
self.login(person_user_id)
self.personRequestInstanceNotReady(
software_release=software_release,
software_type=software_type,
partition_reference=instance_title,
project_reference=project_reference
)
self.tic()
instance_tree = self.portal.portal_catalog.getResultValue(
portal_type="Instance Tree",
title=instance_title,
follow_up__reference=project_reference
)
person = instance_tree.getDestinationSectionValue()
self.assertEqual(person.getUserId(), person_user_id)
subscription_request = self.checkServiceSubscriptionRequest(instance_tree, 'submitted')
self.assertEqual(subscription_request.getTotalPrice(), deposit_amount)
self.tic()
outstanding_amount_list = person.Entity_getOutstandingDepositAmountList(
currency.getUid(), ledger_uid=subscription_request.getLedgerUid())
self.assertEqual(sum([i.total_price for i in outstanding_amount_list]), deposit_amount)
self.login(person.getUserId())
# Ensure to pay from the website
outstanding_amount = self.web_site.restrictedTraverse(outstanding_amount_list[0].getRelativeUrl())
outstanding_amount.Base_createExternalPaymentTransactionFromOutstandingAmountAndRedirect()
self.tic()
self.logout()
self.login()
payment_transaction = self.portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
destination_section_uid=person.getUid(),
simulation_state="started"
)
self.assertEqual(payment_transaction.getSpecialiseValue().getTradeConditionType(), "deposit")
# payzen interface will only stop the payment
payment_transaction.stop()
self.tic()
assert payment_transaction.receivable.getGroupingReference(None) is not None
self.login(person_user_id)
self.checkServiceSubscriptionRequest(instance_tree, 'invalidated')
amount = sum([i.total_price for i in person.Entity_getOutstandingDepositAmountList(
currency.getUid(), ledger_uid=subscription_request.getLedgerUid())])
self.assertEqual(0, amount)
self.login(person_user_id)
self.personRequestInstance(
software_release=software_release,
software_type=software_type,
partition_reference=instance_title,
project_reference=project_reference
)
# now instantiate it on compute_node and set some nice connection dict
self.simulateSlapgridCP(server)
# let's find instances of user and check connection strings
instance_tree_list = [q.getObject() for q in
self._getCurrentInstanceTreeList()
if q.getTitle() == instance_title]
self.assertEqual(1, len(instance_tree_list))
instance_tree = instance_tree_list[0]
software_instance = instance_tree.getSuccessorValue()
self.assertEqual(software_instance.getTitle(),
instance_tree.getTitle())
connection_dict = software_instance.getConnectionXmlAsDict()
self.assertSameSet(('url_1', 'url_2'), connection_dict.keys())
self.login()
partition = software_instance.getAggregateValue()
self.assertSameSet(
['http://%s/' % q.getIpAddress() for q in
partition.contentValues(portal_type='Internet Protocol Address')],
connection_dict.values())
def findMessage(self, email, body): def findMessage(self, email, body):
for candidate in reversed(self.portal.MailHost.getMessageList()): for candidate in reversed(self.portal.MailHost.getMessageList()):
if [q for q in candidate[1] if email in q] and body in candidate[2]: if [q for q in candidate[1] if email in q] and body in candidate[2]:
......
...@@ -56,9 +56,38 @@ class TestSlapOSCRMScenario(TestSlapOSVirtualMasterScenarioMixin): ...@@ -56,9 +56,38 @@ class TestSlapOSCRMScenario(TestSlapOSVirtualMasterScenarioMixin):
################################################## ##################################################
# Add deposit # Add deposit
with PinnedDateTime(self, creation_date): with PinnedDateTime(self, creation_date):
payment_transaction = owner_person.Person_addDepositPayment(99*100, currency.getRelativeUrl(), 1) # Pay deposit to validate virtual master
# payzen interface will only stop the payment self.login(owner_person.getUserId())
deposit_amount = 42.0
ledger = self.portal.portal_categories.ledger.automated
outstanding_amount_list = owner_person.Entity_getOutstandingDepositAmountList(
currency.getUid(), ledger_uid=ledger.getUid())
amount = sum([i.total_price for i in outstanding_amount_list])
self.assertEqual(amount, deposit_amount)
# Ensure to pay from the website
outstanding_amount = self.web_site.restrictedTraverse(outstanding_amount_list[0].getRelativeUrl())
outstanding_amount.Base_createExternalPaymentTransactionFromOutstandingAmountAndRedirect()
self.tic()
self.logout()
self.login()
payment_transaction = self.portal.portal_catalog.getResultValue(
portal_type="Payment Transaction",
destination_section_uid=owner_person.getUid(),
simulation_state="started"
)
self.assertEqual(payment_transaction.getSpecialiseValue().getTradeConditionType(), "deposit")
# payzen/wechat or accountant will only stop the payment
payment_transaction.stop() payment_transaction.stop()
self.tic()
assert payment_transaction.receivable.getGroupingReference(None) is not None
self.login(owner_person.getUserId())
amount = sum([i.total_price for i in owner_person.Entity_getOutstandingDepositAmountList(
currency.getUid(), ledger_uid=ledger.getUid())])
self.assertEqual(0, amount)
################################################## ##################################################
# Add first batch of service, and generate invoices # Add first batch of service, and generate invoices
......
...@@ -7,6 +7,13 @@ ...@@ -7,6 +7,13 @@
<multi_property id='categories'>local_role_group/user</multi_property> <multi_property id='categories'>local_role_group/user</multi_property>
<multi_property id='base_category'>destination_decision</multi_property> <multi_property id='base_category'>destination_decision</multi_property>
</role> </role>
<role id='Auditor'>
<property id='title'>Person Shadow</property>
<property id='condition'>python: context.getLedger("") == "automated"</property>
<multi_property id='categories'>local_role_group/shadow</multi_property>
<multi_property id='category'>role/shadow/person</multi_property>
<multi_property id='base_category'>role</multi_property>
</role>
<role id='Auditor'> <role id='Auditor'>
<property id='title'>Sale Agent</property> <property id='title'>Sale Agent</property>
<multi_property id='categories'>local_role_group/function</multi_property> <multi_property id='categories'>local_role_group/function</multi_property>
......
...@@ -36,10 +36,6 @@ if not [x for x in subscription_assignment_category_list if x.startswith('destin ...@@ -36,10 +36,6 @@ if not [x for x in subscription_assignment_category_list if x.startswith('destin
preference_method_list = [ preference_method_list = [
"getPreferredHateoasUrl", "getPreferredHateoasUrl",
"getPreferredPayzenPaymentServiceReference",
"getPreferredPayzenIntegrationSite",
"getPreferredWechatPaymentServiceReference",
"getPreferredWechatIntegrationSite"
] ]
for method_id in preference_method_list: for method_id in preference_method_list:
......
...@@ -227,6 +227,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template): ...@@ -227,6 +227,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/Base_addSlapOSSupportRequest', 'slapos_panel/Base_addSlapOSSupportRequest',
'slapos_panel/Base_getAuthenticatedPersonUid', 'slapos_panel/Base_getAuthenticatedPersonUid',
'slapos_panel/Base_getNewsDictFromComputeNodeList', 'slapos_panel/Base_getNewsDictFromComputeNodeList',
'slapos_panel/Base_getPaymentModeForCurrency',
'slapos_panel/Base_getStatusMonitorUrl', 'slapos_panel/Base_getStatusMonitorUrl',
'slapos_panel/Base_hasSlapOSProjectUserGroup', 'slapos_panel/Base_hasSlapOSProjectUserGroup',
'slapos_panel/ComputeNode_getBusyComputePartitionList', 'slapos_panel/ComputeNode_getBusyComputePartitionList',
...@@ -249,6 +250,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template): ...@@ -249,6 +250,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/InstanceTree_requestStart', 'slapos_panel/InstanceTree_requestStart',
'slapos_panel/InstanceTree_requestStop', 'slapos_panel/InstanceTree_requestStop',
'slapos_panel/InstanceTree_updateParameter', 'slapos_panel/InstanceTree_updateParameter',
'slapos_panel/PaymentTransaction_redirectToManualPayment',
'slapos_panel/Project_addSlapOSAllocationSupply', 'slapos_panel/Project_addSlapOSAllocationSupply',
'slapos_panel/Project_addSlapOSComputeNode', 'slapos_panel/Project_addSlapOSComputeNode',
'slapos_panel/Project_addSlapOSComputeNodeToken', 'slapos_panel/Project_addSlapOSComputeNodeToken',
...@@ -272,6 +274,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template): ...@@ -272,6 +274,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/SoftwareInstance_getNewsDict', 'slapos_panel/SoftwareInstance_getNewsDict',
'slapos_panel/SoftwareProduct_addSlapOSSoftwareRelease', 'slapos_panel/SoftwareProduct_addSlapOSSoftwareRelease',
'slapos_panel/SoftwareProduct_addSlapOSSoftwareType', 'slapos_panel/SoftwareProduct_addSlapOSSoftwareType',
'slapos_panel/SubscriptionRequest_jumpToPaymentPage',
'slapos_panel/Ticket_addSlapOSEvent', 'slapos_panel/Ticket_addSlapOSEvent',
'slapos_panel/Ticket_closeSlapOS', 'slapos_panel/Ticket_closeSlapOS',
'slapos_panel/Ticket_suspendSlapOS', 'slapos_panel/Ticket_suspendSlapOS',
...@@ -281,7 +284,6 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template): ...@@ -281,7 +284,6 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/InstanceTree_proposeUpgradeDecision', 'slapos_panel/InstanceTree_proposeUpgradeDecision',
'slapos_panel/InstanceTree_searchUpgradableSoftwareReleaseList', 'slapos_panel/InstanceTree_searchUpgradableSoftwareReleaseList',
'slapos_panel/PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel', 'slapos_panel/PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel',
'slapos_panel/SaleInvoiceTransaction_createExternalPaymentTransactionFromAmountAndRedirect',
'slapos_panel_compatibility/Base_getComputerToken', 'slapos_panel_compatibility/Base_getComputerToken',
'slapos_parameter_editor/SoftwareProductModule_updateParameterEditorTestDialog', 'slapos_parameter_editor/SoftwareProductModule_updateParameterEditorTestDialog',
'slapos_parameter_editor/SoftwareProductModule_validateParameterEditorTestDialog', 'slapos_parameter_editor/SoftwareProductModule_validateParameterEditorTestDialog',
......
...@@ -1628,11 +1628,12 @@ class TestSubscriptionRequest(TestSlapOSGroupRoleSecurityMixin): ...@@ -1628,11 +1628,12 @@ class TestSubscriptionRequest(TestSlapOSGroupRoleSecurityMixin):
portal_type='Subscription Request') portal_type='Subscription Request')
delivery.edit(destination_decision_value=person, ledger="automated") delivery.edit(destination_decision_value=person, ledger="automated")
self.assertSecurityGroup(delivery, self.assertSecurityGroup(delivery,
['F-SALE*', self.user_id, person.getUserId()], False) ['F-SALE*', self.user_id, "R-SHADOW-PERSON",
person.getUserId()], False)
self.assertRoles(delivery, self.user_id, ['Owner']) self.assertRoles(delivery, self.user_id, ['Owner'])
self.assertRoles(delivery, 'F-SALE*', ['Auditor']) self.assertRoles(delivery, 'F-SALE*', ['Auditor'])
self.assertRoles(delivery, person.getUserId(), ['Associate']) self.assertRoles(delivery, person.getUserId(), ['Associate'])
self.assertRoles(delivery, 'R-SHADOW-PERSON', ['Auditor'])
class TestOrganisationModule(TestSlapOSGroupRoleSecurityMixin): class TestOrganisationModule(TestSlapOSGroupRoleSecurityMixin):
def test_OrganisationModule(self): def test_OrganisationModule(self):
......
...@@ -2,91 +2,99 @@ ...@@ -2,91 +2,99 @@
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="Payzen Service" module="erp5.portal_type"/> <global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>_count</string> </key> <key> <string>action</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value> </value>
</item> </item>
<item> <item>
<key> <string>_mt_index</string> </key> <key> <string>categories</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent> <tuple>
</value> <string>action_type/object_jio_jump</string>
</item> </tuple>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value> </value>
</item> </item>
<item> <item>
<key> <string>default_reference</string> </key> <key> <string>category</string> </key>
<value> <string>PSERV-Payzen-Test</string> </value> <value> <string>object_jio_jump</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>condition</string> </key>
<value> <string>slapos_payzen_test</string> </value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item> </item>
<item> <item>
<key> <string>payzen_vads_action_mode</string> </key> <key> <string>description</string> </key>
<value> <string>INTERACTIVE</string> </value> <value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>payzen_vads_ctx_mode</string> </key> <key> <string>icon</string> </key>
<value> <string>TEST</string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>payzen_vads_page_action</string> </key> <key> <string>id</string> </key>
<value> <string>PAYMENT</string> </value> <value> <string>jump_pay_my_slapos_sale_invoice_transaction</string> </value>
</item> </item>
<item> <item>
<key> <string>payzen_vads_version</string> </key> <key> <string>permissions</string> </key>
<value> <string>V2</string> </value> <value>
<tuple>
<string>View</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
<value> <string>Payzen Service</string> </value> <value> <string>Action Information</string> </value>
</item> </item>
<item> <item>
<key> <string>service_password</string> </key> <key> <string>priority</string> </key>
<value> <string>bar</string> </value> <value> <float>20.0</float> </value>
</item> </item>
<item> <item>
<key> <string>service_username</string> </key> <key> <string>title</string> </key>
<value> <string>foo</string> </value> <value> <string>Pay</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>visible</string> </key>
<value> <string>PayZen</string> </value> <value> <int>1</int> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="2" aka="AAAAAAAAAAI="> <record id="2" aka="AAAAAAAAAAI=">
<pickle> <pickle>
<global name="Length" module="BTrees.Length"/> <global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle> </pickle>
<pickle> <pickle>
<none/> <dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SubscriptionRequest_jumpToPaymentPage</string> </value>
</item>
</dictionary>
</pickle> </pickle>
</record> </record>
<record id="4" aka="AAAAAAAAAAQ="> <record id="3" aka="AAAAAAAAAAM=">
<pickle> <pickle>
<global name="OOBTree" module="BTrees.OOBTree"/> <global name="Expression" module="Products.CMFCore.Expression"/>
</pickle> </pickle>
<pickle> <pickle>
<none/> <dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: (context.getWebSiteValue() is not None) and (context.getSimulationState() == \'submitted\')</string> </value>
</item>
</dictionary>
</pickle> </pickle>
</record> </record>
</ZopeData> </ZopeData>
...@@ -363,16 +363,6 @@ ...@@ -363,16 +363,6 @@
<value> <string>string</string> </value> <value> <string>string</string> </value>
</item> </item>
</dictionary> </dictionary>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>configuration_payment_url_template</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>string</string> </value>
</item>
</dictionary>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -467,12 +457,6 @@ ...@@ -467,12 +457,6 @@
<key> <string>configuration_panel_gadget_url</string> </key> <key> <string>configuration_panel_gadget_url</string> </key>
<value> <string>slapos_master_panel_panel.html</string> </value> <value> <string>slapos_master_panel_panel.html</string> </value>
</item> </item>
<item>
<key> <string>configuration_payment_url_template</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>configuration_precache_manifest_script_list</string> </key> <key> <string>configuration_precache_manifest_script_list</string> </key>
<value> <string>WebSection_getJsonEditorPrecacheManifestList\n <value> <string>WebSection_getJsonEditorPrecacheManifestList\n
......
...@@ -14,31 +14,33 @@ ledger_uid = portal.portal_categories.ledger.automated.getUid() ...@@ -14,31 +14,33 @@ ledger_uid = portal.portal_categories.ledger.automated.getUid()
html_content = '' html_content = ''
entity = portal.portal_membership.getAuthenticatedMember().getUserValue() entity = portal.portal_membership.getAuthenticatedMember().getUserValue()
if entity is None: if entity is None:
return '<p>Nothing to pay</p>' return '<p>Nothing to pay with your account</p>'
for currency_uid, secure_service_relative_url in [ for currency_uid, secure_service_relative_url in [
(portal.currency_module.EUR.getUid(), portal.Base_getPayzenServiceRelativeUrl()), (portal.currency_module.EUR.getUid(), portal.Base_getPayzenServiceRelativeUrl()),
# (portal.currency_module.CNY.getUid(), portal.Base_getWechatServiceRelativeUrl()) # (portal.currency_module.CNY.getUid(), portal.Base_getWechatServiceRelativeUrl())
]: ]:
is_payment_configured = 1
if secure_service_relative_url is None:
is_payment_configured = 0
for method in [entity.Entity_getOutstandingAmountList,
entity.Entity_getOutstandingDepositAmountList]:
for outstanding_amount in method(
ledger_uid=ledger_uid, resource_uid=currency_uid):
if 0 < outstanding_amount.total_price:
if not is_payment_configured:
return '<p>Please contact us to handle your payment</p>'
if secure_service_relative_url is not None:
outstanding_amount_list = entity.Entity_getOutstandingAmountList(
ledger_uid=ledger_uid,
resource_uid=currency_uid
)
for outstanding_amount in outstanding_amount_list:
html_content += """ html_content += """
<p><a href="%(payment_url)s">%(total_price)s %(currency)s</a></p> <p><a href="%(payment_url)s">%(total_price)s %(currency)s</a></p>
""" % { """ % {
'total_price': outstanding_amount.total_price, 'total_price': outstanding_amount.total_price,
'currency': outstanding_amount.getPriceCurrencyReference(), 'currency': outstanding_amount.getPriceCurrencyReference(),
'payment_url': '%s/SaleInvoiceTransaction_createExternalPaymentTransactionFromAmountAndRedirect' % outstanding_amount.absolute_url() 'payment_url': '%s/Base_createExternalPaymentTransactionFromOutstandingAmountAndRedirect' % outstanding_amount.absolute_url()
} }
if html_content: if not html_content:
if web_site.getLayoutProperty("configuration_payment_url_template", None) is None:
html_content = '<p>Please contact us to handle your payment</p>'
else:
html_content = '<p>Nothing to pay</p>' html_content = '<p>Nothing to pay</p>'
return html_content return html_content
portal = context.getPortalObject()
# Get entity from context to preserve path
entity = portal.portal_membership.getAuthenticatedMember().getUserValue()
outstanding_amount = context
web_site = outstanding_amount.getWebSiteValue()
assert outstanding_amount.getLedgerUid() == portal.portal_categories.ledger.automated.getUid()
assert outstanding_amount.getDestinationSectionUid() == entity.getUid()
assert web_site is not None
payment_mode = outstanding_amount.Base_getPaymentModeForCurrency(outstanding_amount.getPriceCurrencyUid())
def wrapWithShadow(entity, outstanding_amount, payment_mode):
portal_type = outstanding_amount.getPortalType()
method_kw = dict(
section_uid=outstanding_amount.getSourceSectionUid(),
resource_uid=outstanding_amount.getPriceCurrencyUid(),
ledger_uid=outstanding_amount.getLedgerUid()
)
if portal_type == "Sale Invoice Transaction":
return entity.Entity_createPaymentTransaction(
entity.Entity_getOutstandingAmountList(
group_by_node=False,
**method_kw
),
payment_mode=payment_mode
)
elif portal_type == "Subscription Request":
# We include deposit for Subscription Requests.
return entity.Entity_createDepositPaymentTransaction(
entity.Entity_getOutstandingDepositAmountList(
**method_kw),
payment_mode=payment_mode
)
raise ValueError("Unsupported outstanding amount type: %s" % (portal_type))
# Ensure to re-take the entity under a proper acquisition context
entity = web_site.restrictedTraverse(entity.getRelativeUrl())
payment_transaction = entity.Person_restrictMethodAsShadowUser(
shadow_document=entity,
callable_object=wrapWithShadow,
argument_list=[entity, outstanding_amount, payment_mode])
return payment_transaction.PaymentTransaction_redirectToManualPayment()
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_createExternalPaymentTransactionFromAmountAndRedirect</string> </value> <value> <string>Base_createExternalPaymentTransactionFromOutstandingAmountAndRedirect</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
""" XXX This script expects some re-implementation to
rely on panel configuration for handle payment.
"""
portal = context.getPortalObject()
payment_mode = None
for accepted_currency_uid, accepted_payment_mode, is_activated in [
(portal.currency_module.EUR.getUid(), 'payzen', portal.Base_getPayzenServiceRelativeUrl()),
# (portal.currency_module.CNY, 'wechat', portal.Base_getWechatServiceRelativeUrl())
]:
if is_activated and (currency_uid == accepted_currency_uid):
payment_mode = accepted_payment_mode
return payment_mode
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>currency_uid</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getPaymentModeForCurrency</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
""" Return a dict with vads_urls required for payzen.""" """ Return a dict with vads_urls required for payzen."""
if web_site is None:
web_site = context.getWebSiteValue()
if web_site is None:
raise ValueError("This script must be called from a web site")
base = "%(payment_transaction_url)s/PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel?result=%(result)s" base = "%(payment_transaction_url)s/PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel?result=%(result)s"
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>web_site</string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
if (context.getPaymentMode() == "wechat"):
return context.PaymentTransaction_redirectToManualWechatPayment()
elif (context.getPaymentMode() == "payzen"):
return context.PaymentTransaction_redirectToManualPayzenPayment()
else:
return context.PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel(result="contact_us")
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PaymentTransaction_redirectToManualPayment</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
from DateTime import DateTime
date = DateTime()
entity = portal.portal_membership.getAuthenticatedMember().getUserValue()
outstanding_amount = context
web_site = context.getWebSiteValue()
assert web_site is not None
assert web_site.getLayoutProperty("configuration_payment_url_template", None) is not None
assert outstanding_amount.getLedgerUid() == portal.portal_categories.ledger.automated.getUid()
assert outstanding_amount.getDestinationSectionUid() == entity.getUid()
payment_mode = None
resource_uid = outstanding_amount.getPriceCurrencyUid()
for accepted_resource_uid, accepted_payment_mode, is_activated in [
(portal.currency_module.EUR.getUid(), 'payzen', portal.Base_getPayzenServiceRelativeUrl()),
]:
if is_activated and (resource_uid == accepted_resource_uid):
payment_mode = accepted_payment_mode
assert payment_mode is not None
def wrapWithShadow(entity, outstanding_amount):
return entity.Entity_createPaymentTransaction(
entity.Entity_getOutstandingAmountList(
section_uid=outstanding_amount.getSourceSectionUid(),
resource_uid=outstanding_amount.getPriceCurrencyUid(),
ledger_uid=outstanding_amount.getLedgerUid(),
group_by_node=False
),
start_date=date,
payment_mode=payment_mode
)
entity = outstanding_amount.getDestinationSectionValue(portal_type="Person")
payment_transaction = entity.Person_restrictMethodAsShadowUser(
shadow_document=entity,
callable_object=wrapWithShadow,
argument_list=[entity, outstanding_amount])
web_site = context.getWebSiteValue()
if (payment_mode == "wechat"):
return payment_transaction.PaymentTransaction_redirectToManualWechatPayment(web_site=web_site)
elif (payment_mode == "payzen"):
return payment_transaction.PaymentTransaction_redirectToManualPayzenPayment(web_site=web_site)
else:
raise NotImplementedError('not implemented')
return context.getPortalObject().accounting_module.Base_redirect(
"AccountingTransactionModule_viewCreateExternalPaymentTransactionOnSlaposPanelDialog")
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionRequest_jumpToPaymentPage</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -80,7 +80,6 @@ ...@@ -80,7 +80,6 @@
<string>my_configuration_content_security_policy</string> <string>my_configuration_content_security_policy</string>
<string>my_configuration_x_frame_options</string> <string>my_configuration_x_frame_options</string>
<string>my_configuration_compute_node_install_command_line</string> <string>my_configuration_compute_node_install_command_line</string>
<string>my_configuration_payment_url_template</string>
<string>my_configuration_slapos_master_api</string> <string>my_configuration_slapos_master_api</string>
</list> </list>
</value> </value>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_configuration_payment_url_template</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>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Payment URL Template</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -89,6 +89,7 @@ Software Product | slapos_panel_view ...@@ -89,6 +89,7 @@ Software Product | slapos_panel_view
Software Release Module | slapos_panel_view Software Release Module | slapos_panel_view
Software Release | slapos_panel_view Software Release | slapos_panel_view
Subscription Request Module | slapos_panel_view Subscription Request Module | slapos_panel_view
Subscription Request | jump_pay_my_slapos_sale_invoice_transaction
Subscription Request | slapos_panel_view Subscription Request | slapos_panel_view
Support Request Module | slapos_panel_view Support Request Module | slapos_panel_view
Support Request Module | slapos_panel_view_my_ticket_list Support Request Module | slapos_panel_view_my_ticket_list
......
...@@ -22,7 +22,7 @@ def ERP5Site_activateAlarmSlapOSPanelTest(self): ...@@ -22,7 +22,7 @@ def ERP5Site_activateAlarmSlapOSPanelTest(self):
def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login, def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
manager_login, remote_customer_login, manager_login, remote_customer_login,
passwd): passwd, currency=None):
if step not in ['trade_condition', 'account']: if step not in ['trade_condition', 'account']:
raise ValueError('Unsupported bootstrap step: %s' % step) raise ValueError('Unsupported bootstrap step: %s' % step)
...@@ -45,6 +45,7 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login, ...@@ -45,6 +45,7 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
portal.portal_alarms.upgrader_check_post_upgrade.activeSense(fixit=True) portal.portal_alarms.upgrader_check_post_upgrade.activeSense(fixit=True)
# Currency # Currency
if currency is None:
currency = portal.currency_module.newContent( currency = portal.currency_module.newContent(
portal_type="Currency", portal_type="Currency",
reference="test-currency-%s" % self.generateNewId(), reference="test-currency-%s" % self.generateNewId(),
...@@ -105,10 +106,21 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login, ...@@ -105,10 +106,21 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
trade_condition.validate() trade_condition.validate()
if scenario == 'accounting': if scenario == 'accounting':
# Sale trade condition
# Create trade condition for Deposit
portal.sale_trade_condition_module.newContent(
portal_type="Sale Trade Condition",
reference="Deposit for : %s" % currency.getRelativeUrl(),
trade_condition_type="deposit",
specialise_value=sale_trade_condition,
source_value=organisation,
source_section_value=organisation,
price_currency_value=currency).validate()
# Sale Supply for Virtual Master
sale_supply = portal.sale_supply_module.newContent( sale_supply = portal.sale_supply_module.newContent(
portal_type="Sale Supply", portal_type="Sale Supply",
title="Test project", title="Sale Supply for Virtual Master (%s)" % currency.getRelativeUrl(),
price_currency_value=currency, price_currency_value=currency,
) )
sale_supply.newContent( sale_supply.newContent(
...@@ -117,7 +129,6 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login, ...@@ -117,7 +129,6 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
resource="service_module/slapos_virtual_master_subscription" resource="service_module/slapos_virtual_master_subscription"
) )
sale_supply.validate() sale_supply.validate()
finally: finally:
setSecurityManager(sm) setSecurityManager(sm)
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Link" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>default_link</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Link</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>url_string</string> </key>
<value> <string>https://secure.payzen.eu/vads-payment/</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Link" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>wsdl_link</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Link</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>url_string</string> </key>
<value> <string>https://secure.payzen.eu/vads-ws/v3?wsdl</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -3,7 +3,6 @@ if REQUEST is not None: ...@@ -3,7 +3,6 @@ if REQUEST is not None:
raise Unauthorized raise Unauthorized
portal = context.getPortalObject() portal = context.getPortalObject()
integration_site = portal.restrictedTraverse(portal.portal_preferences.getPreferredPayzenIntegrationSite())
_, transaction_id = context.PaymentTransaction_getPayzenId() _, transaction_id = context.PaymentTransaction_getPayzenId()
if transaction_id is not None: if transaction_id is not None:
...@@ -13,19 +12,17 @@ if transaction_id is not None: ...@@ -13,19 +12,17 @@ if transaction_id is not None:
now = DateTime().toZone('UTC') now = DateTime().toZone('UTC')
today = now.asdatetime().strftime('%Y%m%d') today = now.asdatetime().strftime('%Y%m%d')
preferred_integration_site = portal.portal_preferences.getPreferredPayzenIntegrationSite()
integration_site = portal.restrictedTraverse(preferred_integration_site)
if integration_site is None or not preferred_integration_site:
raise ValueError("Integration Site not found or not configured: %s" %
preferred_integration_site)
transaction_id = str(portal.portal_ids.generateNewId( transaction_id = str(portal.portal_ids.generateNewId(
id_group='%s_%s' % (integration_site.getRelativeUrl(), today), id_group='%s_%s' % (integration_site.getRelativeUrl(), today),
id_generator='uid')).zfill(6) id_generator='uid')).zfill(6)
mapping_id = '%s_%s' % (today, transaction_id)
# integration_site.Causality[mapping_id].setDestinationReference(context.getRelativeUrl())
# try:
# integration_site.getCategoryFromMapping('Causality/%s' % mapping_id, create_mapping_line=True, create_mapping=True)
# except ValueError:
# mapping = integration_site.Causality[mapping_id]
# mapping.setDestinationReference('%s' % context.getRelativeUrl())
# else:
# raise ValueError, "Payzen transaction_id already exists"
try: try:
# Init for use later. # Init for use later.
...@@ -35,6 +32,8 @@ try: ...@@ -35,6 +32,8 @@ try:
create_mapping=True) create_mapping=True)
except ValueError: except ValueError:
pass pass
mapping_id = '%s_%s' % (today, transaction_id)
integration_site.Causality[context.getId().replace('-', '_')].setDestinationReference(mapping_id) integration_site.Causality[context.getId().replace('-', '_')].setDestinationReference(mapping_id)
return context.PaymentTransaction_getPayzenId() return context.PaymentTransaction_getPayzenId()
...@@ -3,7 +3,12 @@ if REQUEST is not None: ...@@ -3,7 +3,12 @@ if REQUEST is not None:
raise Unauthorized raise Unauthorized
portal = context.getPortalObject() portal = context.getPortalObject()
integration_site = portal.restrictedTraverse(portal.portal_preferences.getPreferredPayzenIntegrationSite()) preferred_integration_site = portal.portal_preferences.getPreferredPayzenIntegrationSite()
integration_site = portal.restrictedTraverse(preferred_integration_site)
if integration_site is None or not preferred_integration_site:
raise ValueError("Integration Site not found or not configured: %s" %
preferred_integration_site)
payzen_id = integration_site.getCategoryFromMapping('Causality/%s' % context.getId().replace('-', '_')) payzen_id = integration_site.getCategoryFromMapping('Causality/%s' % context.getId().replace('-', '_'))
if payzen_id != 'causality/%s' % context.getId().replace('-', '_'): if payzen_id != 'causality/%s' % context.getId().replace('-', '_'):
......
...@@ -2,9 +2,9 @@ from zExceptions import Unauthorized ...@@ -2,9 +2,9 @@ from zExceptions import Unauthorized
portal = context.getPortalObject() portal = context.getPortalObject()
person = portal.portal_membership.getAuthenticatedMember().getUserValue() person = portal.portal_membership.getAuthenticatedMember().getUserValue()
def wrapWithShadow(payment_transaction, web_site, person_relative_url): def wrapWithShadow(payment_transaction, person_relative_url):
vads_url_dict = payment_transaction.PaymentTransaction_getVADSUrlDict(web_site) vads_url_dict = payment_transaction.PaymentTransaction_getVADSUrlDict()
_ , transaction_id = payment_transaction.PaymentTransaction_getPayzenId() _ , transaction_id = payment_transaction.PaymentTransaction_getPayzenId()
vads_url_already_registered = vads_url_dict.pop('vads_url_already_registered') vads_url_already_registered = vads_url_dict.pop('vads_url_already_registered')
...@@ -25,10 +25,10 @@ def wrapWithShadow(payment_transaction, web_site, person_relative_url): ...@@ -25,10 +25,10 @@ def wrapWithShadow(payment_transaction, web_site, person_relative_url):
if person is None: if person is None:
if not portal.portal_membership.isAnonymousUser(): if not portal.portal_membership.isAnonymousUser():
return wrapWithShadow(context, web_site, context.getDestinationSection()) return wrapWithShadow(context, context.getDestinationSection())
raise Unauthorized("You must be logged in") raise Unauthorized("You must be logged in")
return person.Person_restrictMethodAsShadowUser( return person.Person_restrictMethodAsShadowUser(
shadow_document=person, shadow_document=person,
callable_object=wrapWithShadow, callable_object=wrapWithShadow,
argument_list=[context, web_site, person.getRelativeUrl()]) argument_list=[context, person.getRelativeUrl()])
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>web_site=None</string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
<title tal:content="here/title"></title> <title tal:content="here/title"></title>
</head> </head>
<body onload="document.payment.submit();"> <body onload="document.payment.submit();">
<center><h2>Redirecting to payment processor...</h2></center>
<p><center><img src="ERP5VCS_imgs/wait.gif"></center></p>
<form method="POST" tal:attributes="action here/link_url_string" id="payment" name="payment"> <form method="POST" tal:attributes="action here/link_url_string" id="payment" name="payment">
<tal:block tal:repeat="value here/field_list"> <tal:block tal:repeat="value here/field_list">
<input type="hidden" tal:attributes="name python: value[0]; value python: value[1]"> <input type="hidden" tal:attributes="name python: value[0]; value python: value[1]">
......
...@@ -19,11 +19,11 @@ ...@@ -19,11 +19,11 @@
# #
############################################################################## ##############################################################################
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort from erp5.component.test.testSlapOSPayzenSkins import TestSlapOSPayzenMixin
from Products.ERP5Type.tests.utils import createZODBPythonScript from Products.ERP5Type.tests.utils import createZODBPythonScript
from DateTime import DateTime from DateTime import DateTime
class TestSlapOSPayzenUpdateStartedPayment(SlapOSTestCaseMixinWithAbort): class TestSlapOSPayzenUpdateStartedPayment(TestSlapOSPayzenMixin):
def test_not_started_payment(self): def test_not_started_payment(self):
new_id = self.generateNewId() new_id = self.generateNewId()
...@@ -81,7 +81,7 @@ class Foo: ...@@ -81,7 +81,7 @@ class Foo:
def updateStatus(self): def updateStatus(self):
context.stop() context.stop()
return Foo() return Foo()
""" ) """)
self.commit() self.commit()
def _simulatePaymentTransaction_createNotPaidPayzenEvent(self): def _simulatePaymentTransaction_createNotPaidPayzenEvent(self):
...@@ -99,7 +99,7 @@ class Foo: ...@@ -99,7 +99,7 @@ class Foo:
def updateStatus(self): def updateStatus(self):
pass pass
return Foo() return Foo()
""" ) """)
self.commit() self.commit()
def _dropPaymentTransaction_createPayzenEvent(self): def _dropPaymentTransaction_createPayzenEvent(self):
...@@ -168,7 +168,7 @@ return Foo() ...@@ -168,7 +168,7 @@ return Foo()
'*args, **kwargs', '*args, **kwargs',
'# Script body\n' '# Script body\n'
"""portal_workflow = context.portal_workflow """portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by PaymentTransaction_updateStatus') """ ) portal_workflow.doActionFor(context, action='edit_action', comment='Visited by PaymentTransaction_updateStatus') """)
self.commit() self.commit()
def _dropPaymentTransaction_updateStatus(self): def _dropPaymentTransaction_updateStatus(self):
......
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved. # -*- coding:utf-8 -*-
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort ##############################################################################
#
# Copyright (c) 2022 Nexedi SA and Contributors. All Rights Reserved.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from erp5.component.test.SlapOSTestCaseMixin import simulate
from erp5.component.test.testSlapOSPayzenSkins import TestSlapOSPayzenMixin
import lxml.html import lxml.html
from DateTime import DateTime from DateTime import DateTime
from Products.ERP5Type.tests.utils import createZODBPythonScript
import difflib import difflib
HARDCODED_PRICE = -99.6 HARDCODED_PRICE = -99.6
...@@ -15,7 +34,14 @@ vads_url_refused = 'http://example.org/refused' ...@@ -15,7 +34,14 @@ vads_url_refused = 'http://example.org/refused'
vads_url_success = 'http://example.org/success' vads_url_success = 'http://example.org/success'
vads_url_return = 'http://example.org/return' vads_url_return = 'http://example.org/return'
class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): class TestSlapOSPayzenInterfaceWorkflow(TestSlapOSPayzenMixin):
def createPayzenService(self):
self.payzen_secure_payment = self.portal.portal_secure_payments.newContent(
portal_type="Payzen Service",
reference="PSERV-Payzen-Test"
)
self.tic()
slapos_payzen_html = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> slapos_payzen_html = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
...@@ -26,6 +52,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -26,6 +52,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
<title>title</title> <title>title</title>
</head> </head>
<body onload="document.payment.submit();"> <body onload="document.payment.submit();">
<center><h2>Redirecting to payment processor...</h2></center>
<p></p><center><img src="ERP5VCS_imgs/wait.gif"></img></center>
<form action="%(action)s" id="payment" method="POST" name="payment"> <form action="%(action)s" id="payment" method="POST" name="payment">
<input name="signature" type="hidden" value="%(signature)s"></input> <input name="signature" type="hidden" value="%(signature)s"></input>
...@@ -94,21 +122,6 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -94,21 +122,6 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
</body> </body>
</html>''' </html>'''
def _simulatePaymentTransaction_getTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\nreturn %f' % HARDCODED_PRICE)
def _dropPaymentTransaction_getTotalPayablePrice(self):
script_name = 'PaymentTransaction_getTotalPayablePrice'
if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name)
def test_generateManualPaymentPage_mandatoryParameters(self): def test_generateManualPaymentPage_mandatoryParameters(self):
event = self.createPayzenEvent() event = self.createPayzenEvent()
# vads_url_cancel # vads_url_cancel
...@@ -175,7 +188,7 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -175,7 +188,7 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
event = self.createPayzenEvent() event = self.createPayzenEvent()
payment = self.createPaymentTransaction() payment = self.createPaymentTransaction()
event.edit(destination_value=payment) event.edit(destination_value=payment)
_ , _ = payment.PaymentTransaction_generatePayzenId() payment.PaymentTransaction_generatePayzenId()
self.assertRaises(ValueError, event.generateManualPaymentPage, self.assertRaises(ValueError, event.generateManualPaymentPage,
vads_url_cancel=vads_url_cancel, vads_url_cancel=vads_url_cancel,
vads_url_error=vads_url_error, vads_url_error=vads_url_error,
...@@ -199,11 +212,12 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -199,11 +212,12 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
) )
def test_generateManualPaymentPage_noCurrency(self): def test_generateManualPaymentPage_noCurrency(self):
self.createPayzenService()
event = self.createPayzenEvent() event = self.createPayzenEvent()
payment = self.createPaymentTransaction() payment = self.createPaymentTransaction()
event.edit( event.edit(
destination_value=payment, destination_value=payment,
source="portal_secure_payments/slapos_payzen_test", source=self.payzen_secure_payment.getRelativeUrl(),
) )
self.assertRaises(AttributeError, event.generateManualPaymentPage, self.assertRaises(AttributeError, event.generateManualPaymentPage,
vads_url_cancel=vads_url_cancel, vads_url_cancel=vads_url_cancel,
...@@ -214,7 +228,21 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -214,7 +228,21 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
vads_url_return=vads_url_return, vads_url_return=vads_url_return,
) )
@simulate("PaymentTransaction_getTotalPayablePrice", '*args, **kwargs',
'# Script body\nreturn %f' % HARDCODED_PRICE)
def test_generateManualPaymentPage_defaultUseCase(self): def test_generateManualPaymentPage_defaultUseCase(self):
self.createPayzenService()
self.payzen_secure_payment.edit(
payzen_vads_action_mode='INTERACTIVE',
payzen_vads_ctx_mode='TEST',
payzen_vads_page_action='PAYMENT',
payzen_vads_version='V2',
link_url_string="https://secure.payzen.eu/vads-payment/",
service_api_key="A",
service_password="B",
service_username="C"
)
self.tic()
event = self.createPayzenEvent() event = self.createPayzenEvent()
payment = self.createPaymentTransaction() payment = self.createPaymentTransaction()
payment.edit( payment.edit(
...@@ -222,12 +250,10 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -222,12 +250,10 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
) )
event.edit( event.edit(
destination_value=payment, destination_value=payment,
source="portal_secure_payments/slapos_payzen_test", source=self.payzen_secure_payment.getRelativeUrl(),
) )
before_date = DateTime() before_date = DateTime()
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
event.generateManualPaymentPage( event.generateManualPaymentPage(
vads_url_cancel=vads_url_cancel, vads_url_cancel=vads_url_cancel,
vads_url_error=vads_url_error, vads_url_error=vads_url_error,
...@@ -236,8 +262,6 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -236,8 +262,6 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
vads_url_success=vads_url_success, vads_url_success=vads_url_success,
vads_url_return=vads_url_return, vads_url_return=vads_url_return,
) )
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
after_date = DateTime() after_date = DateTime()
# Payment start date is modified # Payment start date is modified
...@@ -271,7 +295,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -271,7 +295,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'vads_site_id': 'foo', 'vads_site_id': 'foo',
} }
# Calculate the signature... # Calculate the signature...
self.portal.portal_secure_payments.slapos_payzen_test._getFieldList(data_dict)
self.payzen_secure_payment._getFieldList(data_dict)
data_dict['action'] = 'https://secure.payzen.eu/vads-payment/' data_dict['action'] = 'https://secure.payzen.eu/vads-payment/'
if getattr(self, "custom_slapos_payzen_html", None): if getattr(self, "custom_slapos_payzen_html", None):
...@@ -314,45 +339,30 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort): ...@@ -314,45 +339,30 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
event.edit( event.edit(
destination_value=payment, destination_value=payment,
) )
_ , _ = payment.PaymentTransaction_generatePayzenId() payment.PaymentTransaction_generatePayzenId()
self.assertRaises(AttributeError, event.updateStatus) self.assertRaises(AttributeError, event.updateStatus)
def mockRestGetInfo(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 mockrest_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, 'rest_getInfo', mockrest_getInfo) setattr(self.payzen_secure_payment, 'rest_getInfo', mockrest_getInfo)
try: try:
return method_to_call() return method_to_call()
finally: finally:
del payment_service.rest_getInfo del self.payzen_secure_payment.rest_getInfo
def _simulatePayzenEvent_processUpdate(self):
script_name = 'PayzenEvent_processUpdate'
if script_name in self.portal.portal_skins.custom.objectIds():
raise ValueError('Precondition failed: %s exists in custom' % script_name)
createZODBPythonScript(self.portal.portal_skins.custom,
script_name,
'*args, **kwargs',
'# Script body\n'
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by PayzenEvent_processUpdate') """ )
self.commit()
def _dropPayzenEvent_processUpdate(self):
script_name = 'PayzenEvent_processUpdate'
if script_name in self.portal.portal_skins.custom.objectIds():
self.portal.portal_skins.custom.manage_delObjects(script_name)
self.commit()
@simulate("PayzenEvent_processUpdate", '*args, **kwargs',
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by PayzenEvent_processUpdate') """)
def test_updateStatus_defaultUseCase(self): def test_updateStatus_defaultUseCase(self):
self.createPayzenService()
event = self.createPayzenEvent() event = self.createPayzenEvent()
payment = self.createPaymentTransaction() payment = self.createPaymentTransaction()
event.edit( event.edit(
destination_value=payment, destination_value=payment,
source="portal_secure_payments/slapos_payzen_test", source_value=self.payzen_secure_payment,
) )
transaction_date, transaction_id = \ transaction_date, transaction_id = \
payment.PaymentTransaction_generatePayzenId() payment.PaymentTransaction_generatePayzenId()
...@@ -361,8 +371,6 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P ...@@ -361,8 +371,6 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
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()
try:
self.mockRestGetInfo( self.mockRestGetInfo(
event.updateStatus, event.updateStatus,
(transaction_date.toZone('UTC').asdatetime(), (transaction_date.toZone('UTC').asdatetime(),
...@@ -370,8 +378,6 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P ...@@ -370,8 +378,6 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
.asdatetime().strftime('%Y%m%d'), transaction_id)), .asdatetime().strftime('%Y%m%d'), transaction_id)),
(mocked_data_kw, mocked_sent_text, mocked_received_text), (mocked_data_kw, mocked_sent_text, mocked_received_text),
) )
finally:
self._dropPayzenEvent_processUpdate()
event_message_list = event.contentValues(portal_type="Payzen Event Message") event_message_list = event.contentValues(portal_type="Payzen Event Message")
self.assertEqual(len(event_message_list), 2) self.assertEqual(len(event_message_list), 2)
......
...@@ -4,6 +4,4 @@ portal_integrations/slapos_payzen_test_integration/Causality ...@@ -4,6 +4,4 @@ portal_integrations/slapos_payzen_test_integration/Causality
portal_integrations/slapos_payzen_test_integration/Resource portal_integrations/slapos_payzen_test_integration/Resource
portal_integrations/slapos_payzen_test_integration/Resource/** portal_integrations/slapos_payzen_test_integration/Resource/**
portal_integrations/slapos_payzen_test_integration/SourceProject portal_integrations/slapos_payzen_test_integration/SourceProject
portal_secure_payments/slapos_payzen_test
portal_secure_payments/slapos_payzen_test/**
sale_trade_condition_module/slapos_manual_accounting_trade_condition sale_trade_condition_module/slapos_manual_accounting_trade_condition
\ No newline at end of file
...@@ -20,10 +20,6 @@ if item is None: ...@@ -20,10 +20,6 @@ if item is None:
resource = subscription_request.getResourceValue() resource = subscription_request.getResourceValue()
raise ValueError('Unsupported resource: %s' % resource.getRelativeUrl()) raise ValueError('Unsupported resource: %s' % resource.getRelativeUrl())
# Use list setter, to ensure it crashes if item is still None
# subscription_request.setAggregateValueList([item])
# If the virtual master is not in the expected subscription status, # If the virtual master is not in the expected subscription status,
# do not accept any new service (compute node, instance) for it # do not accept any new service (compute node, instance) for it
if (((subscription_request.getSourceProjectValue() is not None) and if (((subscription_request.getSourceProjectValue() is not None) and
...@@ -37,55 +33,17 @@ if (((subscription_request.getSourceProjectValue() is not None) and ...@@ -37,55 +33,17 @@ if (((subscription_request.getSourceProjectValue() is not None) and
total_price = subscription_request.getTotalPrice() total_price = subscription_request.getTotalPrice()
if 0 < total_price: if 0 < total_price:
# Check that user has enough guarantee deposit to request a new service customer = subscription_request.getDestinationSectionValue()
portal = context.getPortalObject() balance = customer.Entity_getDepositBalanceAmount([subscription_request])
assert_price_kw = {
'resource_uid': subscription_request.getPriceCurrencyUid(),
'portal_type': portal.getPortalAccountingMovementTypeList(),
'ledger_uid': portal.portal_categories.ledger.automated.getUid(),
}
deposit_amount = portal.portal_simulation.getInventoryAssetPrice(
section_uid= subscription_request.getDestinationSectionUid(),
mirror_section_uid= subscription_request.getSourceSectionUid(),
mirror_node_uid=portal.restrictedTraverse('account_module/deposit_received').getUid(),
#node_category_strict_membership=['account_type/income'],
simulation_state= ('stopped', 'delivered'),
# Do not gather deposit reimburse
# when it does not yet have a grouping_reference
omit_asset_decrease=1,
grouping_reference=None,
#src__=1,
**assert_price_kw
)
#return deposit_amount
payable_amount = portal.portal_simulation.getInventoryAssetPrice(
mirror_section_uid= subscription_request.getDestinationSectionUid(),
section_uid= subscription_request.getSourceSectionUid(),
# Do not gather deposit receivable
# when it does not yet have a grouping_reference
omit_asset_decrease=1,
node_category_strict_membership=['account_type/asset/receivable',
'account_type/liability/payable'],
simulation_state= ('planned', 'confirmed', 'started', 'stopped', 'delivered'),
grouping_reference=None,
**assert_price_kw
)
# XXX what is the guarantee deposit account_type? # XXX what is the guarantee deposit account_type?
if deposit_amount < payable_amount + total_price: if balance < total_price:
# if not enough, user will have to pay a deposit for the subscription
# XXX probably create an event asking for a deposit
#pass
return markHistory(subscription_request, return markHistory(subscription_request,
'Not enough deposit from user') 'Your user does not have enough deposit.')
# raise NotImplementedError('NO deposit_amount %s\npayable_amount %s\ntotal_price %s' % (deposit_amount, payable_amount, total_price))
#return 'YES deposit_amount %s\npayable_amount %s\ntotal_price %s' % (deposit_amount, payable_amount, total_price)
if subscription_request.checkConsistency(): if subscription_request.checkConsistency():
return markHistory(subscription_request, str(subscription_request.checkConsistency()[0].getTranslatedMessage())) return markHistory(subscription_request,
str(subscription_request.checkConsistency()[0].getTranslatedMessage()))
subscription_request.SubscriptionRequest_createOpenSaleOrder() subscription_request.SubscriptionRequest_createOpenSaleOrder()
subscription_request.validate() subscription_request.validate()
......
...@@ -3,7 +3,13 @@ if REQUEST is not None: ...@@ -3,7 +3,13 @@ if REQUEST is not None:
raise Unauthorized raise Unauthorized
portal = context.getPortalObject() portal = context.getPortalObject()
integration_site = portal.restrictedTraverse(portal.portal_preferences.getPreferredWechatIntegrationSite())
preferred_integration_site = portal.portal_preferences.getPreferredWechatIntegrationSite()
integration_site = portal.restrictedTraverse(preferred_integration_site)
if integration_site is None or not preferred_integration_site:
raise ValueError("Integration Site not found or not configured: %s" %
preferred_integration_site)
wechat_id = integration_site.getCategoryFromMapping('Causality/%s' % context.getId().replace('-', '_')) wechat_id = integration_site.getCategoryFromMapping('Causality/%s' % context.getId().replace('-', '_'))
if wechat_id != 'causality/%s' % context.getId().replace('-', '_'): if wechat_id != 'causality/%s' % context.getId().replace('-', '_'):
......
...@@ -4,7 +4,7 @@ person = portal.portal_membership.getAuthenticatedMember().getUserValue() ...@@ -4,7 +4,7 @@ person = portal.portal_membership.getAuthenticatedMember().getUserValue()
def wrapWithShadow(payment_transaction, web_site, person_relative_url): def wrapWithShadow(payment_transaction, web_site, person_relative_url):
vads_url_dict = payment_transaction.PaymentTransaction_getVADSUrlDict(web_site) vads_url_dict = payment_transaction.PaymentTransaction_getVADSUrlDict()
_ , transaction_id = payment_transaction.PaymentTransaction_getWechatId() _ , transaction_id = payment_transaction.PaymentTransaction_getWechatId()
vads_url_already_registered = vads_url_dict.pop('vads_url_already_registered') vads_url_already_registered = vads_url_dict.pop('vads_url_already_registered')
......
...@@ -553,11 +553,6 @@ class TestSlapOSWechatBase_getWechatServiceRelativeUrl(SlapOSTestCaseMixinWithAb ...@@ -553,11 +553,6 @@ class TestSlapOSWechatBase_getWechatServiceRelativeUrl(SlapOSTestCaseMixinWithAb
class TestSlapOSWechatPaymentTransaction_redirectToManualWechatPayment( class TestSlapOSWechatPaymentTransaction_redirectToManualWechatPayment(
SlapOSTestCaseMixinWithAbort): SlapOSTestCaseMixinWithAbort):
def test_PaymentTransaction_redirectToManualWechatPayment(self):
payment = self.createPaymentTransaction()
self.assertRaises(ValueError, payment.PaymentTransaction_redirectToManualWechatPayment)
def _simulatePaymentTransaction_getVADSUrlDict(self): def _simulatePaymentTransaction_getVADSUrlDict(self):
script_name = 'PaymentTransaction_getVADSUrlDict' script_name = 'PaymentTransaction_getVADSUrlDict'
if script_name in self.portal.portal_skins.custom.objectIds(): if script_name in self.portal.portal_skins.custom.objectIds():
......
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