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 Products.ERP5Type.Message import translateString
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
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
now = DateTime()
module = portal.portal_trash
tmp_sale_order = module.newContent(
portal_type='Sale Order',
temp_object=True,
trade_condition_type="deposit",
start_date=now,
source=first_subscription.getSource(),
source_section=first_subscription.getSourceSection(),
destination_value=source_section,
destination_section_value=source_section,
destination_decision_value=source_section,
ledger_value=portal.portal_categories.ledger.automated,
ledger=ledger_relative_url,
price_currency=currency_relative_url
)
tmp_sale_order.SaleOrder_applySaleTradeCondition(batch_mode=1, force=1)
......@@ -36,14 +84,20 @@ if (tmp_sale_order.getSourceSection(None) == tmp_sale_order.getDestinationSectio
(tmp_sale_order.getSourceSection(None) is None):
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",
portal_type="Payment Transaction",
start_date=now,
stop_date=now,
specialise_value=tmp_sale_order.getSpecialiseValue(),
source=tmp_sale_order.getSource(),
source_section=tmp_sale_order.getSourceSection(),
......@@ -53,11 +107,11 @@ payment_transaction = portal.accounting_module.newContent(
destination_section=tmp_sale_order.getDestinationSection(),
destination_decision=tmp_sale_order.getDestinationDecision(),
destination_project=tmp_sale_order.getDestinationProject(),
ledger_value=portal.portal_categories.ledger.automated,
payment_mode=payment_mode,
ledger_value=ledger_relative_url,
resource=tmp_sale_order.getPriceCurrency(),
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
......@@ -68,7 +122,8 @@ payment_transaction.newContent(
portal_type='Accounting Transaction Line',
quantity=price,
source_value=getAccountForUse('asset_receivable_subscriber'),
destination_value=getAccountForUse('payable')
destination_value=getAccountForUse('payable'),
activate_kw=activate_kw
)
# bank
......@@ -78,15 +133,16 @@ payment_transaction.newContent(
portal_type='Accounting Transaction Line',
quantity=-price,
source_value=collection_account,
destination_value=collection_account
destination_value=collection_account,
activate_kw=activate_kw
)
if len(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.")
payment_transaction.start(comment=comment)
# Set a flag on the request for prevent 2 calls on the same transaction
context.REQUEST.set(payment_tag, 1)
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):
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()
......
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 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>price, currency_relative_url, batch=0</string> </value>
<value> <string>subscription_list, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_addDepositPayment</string> </value>
<value> <string>Entity_getDepositBalanceAmount</string> </value>
</item>
</dictionary>
</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
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):
new_id = self.generateNewId()
return self.portal.hosting_subscription_module.newContent(
......@@ -157,12 +185,28 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
)
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()
payment.PaymentTransaction_generatePayzenId()
self.assertRaises(
ValueError,
invoice.SaleInvoiceTransaction_createReversalSaleInvoiceTransaction,
batch_mode=1)
finally:
self.portal.portal_integrations.manage_delObjects(
ids=[integration_site.getId()])
system_preference.setPreferredPayzenIntegrationSite(
older_integration_site
)
@withAbort
def test_createReversalSaleInvoiceTransaction_ok(self, payment_mode='payzen'):
......@@ -391,11 +435,27 @@ class TestSlapOSAccounting(SlapOSTestCaseMixin):
created_by_builder=1 # to prevent init script to create lines
)
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()
self.tic()
self.login(person.getUserId())
self.assertEqual("Waiting for payment confirmation",
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):
project = self.addProject()
......
......@@ -17,7 +17,7 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
User does not pay the subscription, which is cancelled after some time
"""
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
# 2 assignment
# 2 sale trade condition
......@@ -32,9 +32,14 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
aggregate__uid=project.getUid()
)
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')):
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
payment_transaction.stop()
self.tic()
......@@ -209,9 +214,24 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
# Add deposit
with PinnedDateTime(self, creation_date + 2):
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
payment_transaction.stop()
self.tic()
##################################################
# Add first batch of service, and generate invoices
......@@ -299,7 +319,7 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
"""
creation_date = DateTime('2020/02/19')
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')
# Ensure no unexpected object has been created
......@@ -310,10 +330,16 @@ class TestSlapOSAccountingScenario(TestSlapOSVirtualMasterScenarioMixin):
##################################################
# 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):
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
payment_transaction.stop()
self.tic()
self.logout()
self.login()
......
......@@ -245,14 +245,6 @@
<key> <string>preferred_password_lifetime_expire_warning_duration</string> </key>
<value> <int>336</int> </value>
</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>
<key> <string>preferred_registration_resource</string> </key>
<value> <string>service_module/vifib_registration</string> </value>
......
......@@ -581,15 +581,17 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
if q.getTitle() == instance_title]
self.assertEqual(0, len(instance_tree_list))
def checkServiceSubscriptionRequest(self, service):
def checkServiceSubscriptionRequest(self, service, simulation_state='invalidated'):
self.login()
subscription_request = self.portal.portal_catalog.getResultValue(
portal_type="Subscription Request",
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,
instance_title, software_release, software_type, server,
......@@ -644,6 +646,96 @@ class DefaultScenarioMixin(TestSlapOSSecurityMixin):
partition.contentValues(portal_type='Internet Protocol Address')],
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):
for candidate in reversed(self.portal.MailHost.getMessageList()):
if [q for q in candidate[1] if email in q] and body in candidate[2]:
......
......@@ -56,9 +56,38 @@ class TestSlapOSCRMScenario(TestSlapOSVirtualMasterScenarioMixin):
##################################################
# Add deposit
with PinnedDateTime(self, creation_date):
payment_transaction = owner_person.Person_addDepositPayment(99*100, currency.getRelativeUrl(), 1)
# payzen interface will only stop the payment
# Pay deposit to validate virtual master
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()
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
......
......@@ -7,6 +7,13 @@
<multi_property id='categories'>local_role_group/user</multi_property>
<multi_property id='base_category'>destination_decision</multi_property>
</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'>
<property id='title'>Sale Agent</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
preference_method_list = [
"getPreferredHateoasUrl",
"getPreferredPayzenPaymentServiceReference",
"getPreferredPayzenIntegrationSite",
"getPreferredWechatPaymentServiceReference",
"getPreferredWechatIntegrationSite"
]
for method_id in preference_method_list:
......
......@@ -227,6 +227,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/Base_addSlapOSSupportRequest',
'slapos_panel/Base_getAuthenticatedPersonUid',
'slapos_panel/Base_getNewsDictFromComputeNodeList',
'slapos_panel/Base_getPaymentModeForCurrency',
'slapos_panel/Base_getStatusMonitorUrl',
'slapos_panel/Base_hasSlapOSProjectUserGroup',
'slapos_panel/ComputeNode_getBusyComputePartitionList',
......@@ -249,6 +250,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/InstanceTree_requestStart',
'slapos_panel/InstanceTree_requestStop',
'slapos_panel/InstanceTree_updateParameter',
'slapos_panel/PaymentTransaction_redirectToManualPayment',
'slapos_panel/Project_addSlapOSAllocationSupply',
'slapos_panel/Project_addSlapOSComputeNode',
'slapos_panel/Project_addSlapOSComputeNodeToken',
......@@ -272,6 +274,7 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/SoftwareInstance_getNewsDict',
'slapos_panel/SoftwareProduct_addSlapOSSoftwareRelease',
'slapos_panel/SoftwareProduct_addSlapOSSoftwareType',
'slapos_panel/SubscriptionRequest_jumpToPaymentPage',
'slapos_panel/Ticket_addSlapOSEvent',
'slapos_panel/Ticket_closeSlapOS',
'slapos_panel/Ticket_suspendSlapOS',
......@@ -281,7 +284,6 @@ def makeTestSlapOSCodingStyleTestCase(tested_business_template):
'slapos_panel/InstanceTree_proposeUpgradeDecision',
'slapos_panel/InstanceTree_searchUpgradableSoftwareReleaseList',
'slapos_panel/PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel',
'slapos_panel/SaleInvoiceTransaction_createExternalPaymentTransactionFromAmountAndRedirect',
'slapos_panel_compatibility/Base_getComputerToken',
'slapos_parameter_editor/SoftwareProductModule_updateParameterEditorTestDialog',
'slapos_parameter_editor/SoftwareProductModule_validateParameterEditorTestDialog',
......
......@@ -1628,11 +1628,12 @@ class TestSubscriptionRequest(TestSlapOSGroupRoleSecurityMixin):
portal_type='Subscription Request')
delivery.edit(destination_decision_value=person, ledger="automated")
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, 'F-SALE*', ['Auditor'])
self.assertRoles(delivery, person.getUserId(), ['Associate'])
self.assertRoles(delivery, 'R-SHADOW-PERSON', ['Auditor'])
class TestOrganisationModule(TestSlapOSGroupRoleSecurityMixin):
def test_OrganisationModule(self):
......
......@@ -2,91 +2,99 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Payzen Service" module="erp5.portal_type"/>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<key> <string>categories</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<tuple>
<string>action_type/object_jio_jump</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>PSERV-Payzen-Test</string> </value>
<key> <string>category</string> </key>
<value> <string>object_jio_jump</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_payzen_test</string> </value>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>payzen_vads_action_mode</string> </key>
<value> <string>INTERACTIVE</string> </value>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>payzen_vads_ctx_mode</string> </key>
<value> <string>TEST</string> </value>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>payzen_vads_page_action</string> </key>
<value> <string>PAYMENT</string> </value>
<key> <string>id</string> </key>
<value> <string>jump_pay_my_slapos_sale_invoice_transaction</string> </value>
</item>
<item>
<key> <string>payzen_vads_version</string> </key>
<value> <string>V2</string> </value>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Payzen Service</string> </value>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>service_password</string> </key>
<value> <string>bar</string> </value>
<key> <string>priority</string> </key>
<value> <float>20.0</float> </value>
</item>
<item>
<key> <string>service_username</string> </key>
<value> <string>foo</string> </value>
<key> <string>title</string> </key>
<value> <string>Pay</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>PayZen</string> </value>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<none/>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SubscriptionRequest_jumpToPaymentPage</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
<global name="Expression" module="Products.CMFCore.Expression"/>
</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>
</record>
</ZopeData>
......@@ -363,16 +363,6 @@
<value> <string>string</string> </value>
</item>
</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>
</value>
</item>
......@@ -467,12 +457,6 @@
<key> <string>configuration_panel_gadget_url</string> </key>
<value> <string>slapos_master_panel_panel.html</string> </value>
</item>
<item>
<key> <string>configuration_payment_url_template</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>configuration_precache_manifest_script_list</string> </key>
<value> <string>WebSection_getJsonEditorPrecacheManifestList\n
......
......@@ -14,31 +14,33 @@ ledger_uid = portal.portal_categories.ledger.automated.getUid()
html_content = ''
entity = portal.portal_membership.getAuthenticatedMember().getUserValue()
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 [
(portal.currency_module.EUR.getUid(), portal.Base_getPayzenServiceRelativeUrl()),
# (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 += """
<p><a href="%(payment_url)s">%(total_price)s %(currency)s</a></p>
""" % {
'total_price': outstanding_amount.total_price,
'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 web_site.getLayoutProperty("configuration_payment_url_template", None) is None:
html_content = '<p>Please contact us to handle your payment</p>'
else:
if not html_content:
html_content = '<p>Nothing to pay</p>'
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 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_createExternalPaymentTransactionFromAmountAndRedirect</string> </value>
<value> <string>Base_createExternalPaymentTransactionFromOutstandingAmountAndRedirect</string> </value>
</item>
</dictionary>
</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."""
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"
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>web_site</string> </value>
<value> <string></string> </value>
</item>
<item>
<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 @@
<string>my_configuration_content_security_policy</string>
<string>my_configuration_x_frame_options</string>
<string>my_configuration_compute_node_install_command_line</string>
<string>my_configuration_payment_url_template</string>
<string>my_configuration_slapos_master_api</string>
</list>
</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
Software Release Module | slapos_panel_view
Software Release | slapos_panel_view
Subscription Request Module | slapos_panel_view
Subscription Request | jump_pay_my_slapos_sale_invoice_transaction
Subscription Request | slapos_panel_view
Support Request Module | slapos_panel_view
Support Request Module | slapos_panel_view_my_ticket_list
......
......@@ -22,7 +22,7 @@ def ERP5Site_activateAlarmSlapOSPanelTest(self):
def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
manager_login, remote_customer_login,
passwd):
passwd, currency=None):
if step not in ['trade_condition', 'account']:
raise ValueError('Unsupported bootstrap step: %s' % step)
......@@ -45,6 +45,7 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
portal.portal_alarms.upgrader_check_post_upgrade.activeSense(fixit=True)
# Currency
if currency is None:
currency = portal.currency_module.newContent(
portal_type="Currency",
reference="test-currency-%s" % self.generateNewId(),
......@@ -105,10 +106,21 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
trade_condition.validate()
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(
portal_type="Sale Supply",
title="Test project",
title="Sale Supply for Virtual Master (%s)" % currency.getRelativeUrl(),
price_currency_value=currency,
)
sale_supply.newContent(
......@@ -117,7 +129,6 @@ def ERP5Site_bootstrapSlapOSPanelTest(self, step, scenario, customer_login,
resource="service_module/slapos_virtual_master_subscription"
)
sale_supply.validate()
finally:
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:
raise Unauthorized
portal = context.getPortalObject()
integration_site = portal.restrictedTraverse(portal.portal_preferences.getPreferredPayzenIntegrationSite())
_, transaction_id = context.PaymentTransaction_getPayzenId()
if transaction_id is not None:
......@@ -13,19 +12,17 @@ if transaction_id is not None:
now = DateTime().toZone('UTC')
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(
id_group='%s_%s' % (integration_site.getRelativeUrl(), today),
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:
# Init for use later.
......@@ -35,6 +32,8 @@ try:
create_mapping=True)
except ValueError:
pass
mapping_id = '%s_%s' % (today, transaction_id)
integration_site.Causality[context.getId().replace('-', '_')].setDestinationReference(mapping_id)
return context.PaymentTransaction_getPayzenId()
......@@ -3,7 +3,12 @@ if REQUEST is not None:
raise Unauthorized
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('-', '_'))
if payzen_id != 'causality/%s' % context.getId().replace('-', '_'):
......
......@@ -2,9 +2,9 @@ from zExceptions import Unauthorized
portal = context.getPortalObject()
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()
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):
if person is None:
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")
return person.Person_restrictMethodAsShadowUser(
shadow_document=person,
callable_object=wrapWithShadow,
argument_list=[context, web_site, person.getRelativeUrl()])
argument_list=[context, person.getRelativeUrl()])
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>web_site=None</string> </value>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -7,6 +7,8 @@
<title tal:content="here/title"></title>
</head>
<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">
<tal:block tal:repeat="value here/field_list">
<input type="hidden" tal:attributes="name python: value[0]; value python: value[1]">
......
......@@ -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 DateTime import DateTime
class TestSlapOSPayzenUpdateStartedPayment(SlapOSTestCaseMixinWithAbort):
class TestSlapOSPayzenUpdateStartedPayment(TestSlapOSPayzenMixin):
def test_not_started_payment(self):
new_id = self.generateNewId()
......@@ -81,7 +81,7 @@ class Foo:
def updateStatus(self):
context.stop()
return Foo()
""" )
""")
self.commit()
def _simulatePaymentTransaction_createNotPaidPayzenEvent(self):
......@@ -99,7 +99,7 @@ class Foo:
def updateStatus(self):
pass
return Foo()
""" )
""")
self.commit()
def _dropPaymentTransaction_createPayzenEvent(self):
......@@ -168,7 +168,7 @@ return Foo()
'*args, **kwargs',
'# Script body\n'
"""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()
def _dropPaymentTransaction_updateStatus(self):
......
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort
# -*- coding:utf-8 -*-
##############################################################################
#
# 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
from DateTime import DateTime
from Products.ERP5Type.tests.utils import createZODBPythonScript
import difflib
HARDCODED_PRICE = -99.6
......@@ -15,7 +34,14 @@ vads_url_refused = 'http://example.org/refused'
vads_url_success = 'http://example.org/success'
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">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
......@@ -26,6 +52,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
<title>title</title>
</head>
<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">
<input name="signature" type="hidden" value="%(signature)s"></input>
......@@ -94,21 +122,6 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
</body>
</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):
event = self.createPayzenEvent()
# vads_url_cancel
......@@ -175,7 +188,7 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
event = self.createPayzenEvent()
payment = self.createPaymentTransaction()
event.edit(destination_value=payment)
_ , _ = payment.PaymentTransaction_generatePayzenId()
payment.PaymentTransaction_generatePayzenId()
self.assertRaises(ValueError, event.generateManualPaymentPage,
vads_url_cancel=vads_url_cancel,
vads_url_error=vads_url_error,
......@@ -199,11 +212,12 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
)
def test_generateManualPaymentPage_noCurrency(self):
self.createPayzenService()
event = self.createPayzenEvent()
payment = self.createPaymentTransaction()
event.edit(
destination_value=payment,
source="portal_secure_payments/slapos_payzen_test",
source=self.payzen_secure_payment.getRelativeUrl(),
)
self.assertRaises(AttributeError, event.generateManualPaymentPage,
vads_url_cancel=vads_url_cancel,
......@@ -214,7 +228,21 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
vads_url_return=vads_url_return,
)
@simulate("PaymentTransaction_getTotalPayablePrice", '*args, **kwargs',
'# Script body\nreturn %f' % HARDCODED_PRICE)
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()
payment = self.createPaymentTransaction()
payment.edit(
......@@ -222,12 +250,10 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
)
event.edit(
destination_value=payment,
source="portal_secure_payments/slapos_payzen_test",
source=self.payzen_secure_payment.getRelativeUrl(),
)
before_date = DateTime()
self._simulatePaymentTransaction_getTotalPayablePrice()
try:
event.generateManualPaymentPage(
vads_url_cancel=vads_url_cancel,
vads_url_error=vads_url_error,
......@@ -236,8 +262,6 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
vads_url_success=vads_url_success,
vads_url_return=vads_url_return,
)
finally:
self._dropPaymentTransaction_getTotalPayablePrice()
after_date = DateTime()
# Payment start date is modified
......@@ -271,7 +295,8 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
'vads_site_id': 'foo',
}
# 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/'
if getattr(self, "custom_slapos_payzen_html", None):
......@@ -314,45 +339,30 @@ class TestSlapOSPayzenInterfaceWorkflow(SlapOSTestCaseMixinWithAbort):
event.edit(
destination_value=payment,
)
_ , _ = payment.PaymentTransaction_generatePayzenId()
payment.PaymentTransaction_generatePayzenId()
self.assertRaises(AttributeError, event.updateStatus)
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):
self.assertEqual(arg1, expected_args[0])
self.assertEqual(arg2, expected_args[1])
return result_tuple
setattr(payment_service, 'rest_getInfo', mockrest_getInfo)
setattr(self.payzen_secure_payment, 'rest_getInfo', mockrest_getInfo)
try:
return method_to_call()
finally:
del payment_service.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()
del self.payzen_secure_payment.rest_getInfo
@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):
self.createPayzenService()
event = self.createPayzenEvent()
payment = self.createPaymentTransaction()
event.edit(
destination_value=payment,
source="portal_secure_payments/slapos_payzen_test",
source_value=self.payzen_secure_payment,
)
transaction_date, transaction_id = \
payment.PaymentTransaction_generatePayzenId()
......@@ -361,8 +371,6 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
mocked_sent_text = 'mocked_sent_text'
mocked_received_text = 'mocked_received_text'
self._simulatePayzenEvent_processUpdate()
try:
self.mockRestGetInfo(
event.updateStatus,
(transaction_date.toZone('UTC').asdatetime(),
......@@ -370,8 +378,6 @@ portal_workflow.doActionFor(context, action='edit_action', comment='Visited by P
.asdatetime().strftime('%Y%m%d'), transaction_id)),
(mocked_data_kw, mocked_sent_text, mocked_received_text),
)
finally:
self._dropPayzenEvent_processUpdate()
event_message_list = event.contentValues(portal_type="Payzen Event Message")
self.assertEqual(len(event_message_list), 2)
......
......@@ -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/SourceProject
portal_secure_payments/slapos_payzen_test
portal_secure_payments/slapos_payzen_test/**
sale_trade_condition_module/slapos_manual_accounting_trade_condition
\ No newline at end of file
......@@ -20,10 +20,6 @@ if item is None:
resource = subscription_request.getResourceValue()
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,
# do not accept any new service (compute node, instance) for it
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()
if 0 < total_price:
# Check that user has enough guarantee deposit to request a new service
portal = context.getPortalObject()
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
)
customer = subscription_request.getDestinationSectionValue()
balance = customer.Entity_getDepositBalanceAmount([subscription_request])
# XXX what is the guarantee deposit account_type?
if deposit_amount < payable_amount + 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
if balance < total_price:
return markHistory(subscription_request,
'Not enough deposit from user')
# 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)
'Your user does not have enough deposit.')
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.validate()
......
......@@ -3,7 +3,13 @@ if REQUEST is not None:
raise Unauthorized
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('-', '_'))
if wechat_id != 'causality/%s' % context.getId().replace('-', '_'):
......
......@@ -4,7 +4,7 @@ person = portal.portal_membership.getAuthenticatedMember().getUserValue()
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()
vads_url_already_registered = vads_url_dict.pop('vads_url_already_registered')
......
......@@ -553,11 +553,6 @@ class TestSlapOSWechatBase_getWechatServiceRelativeUrl(SlapOSTestCaseMixinWithAb
class TestSlapOSWechatPaymentTransaction_redirectToManualWechatPayment(
SlapOSTestCaseMixinWithAbort):
def test_PaymentTransaction_redirectToManualWechatPayment(self):
payment = self.createPaymentTransaction()
self.assertRaises(ValueError, payment.PaymentTransaction_redirectToManualWechatPayment)
def _simulatePaymentTransaction_getVADSUrlDict(self):
script_name = 'PaymentTransaction_getVADSUrlDict'
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