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

Pay deposity for payable subscription requests

See merge request nexedi/slapos.core!629
parents 0941e532 cb300deb
Pipeline #35008 failed with stage
in 0 seconds
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